Rename package and maven coordinates for major release
diff --git a/NOTICE.txt b/NOTICE.txt
index fe4cbd7..ed776f5 100644
--- a/NOTICE.txt
+++ b/NOTICE.txt
@@ -1,5 +1,5 @@
 Apache Commons JCS

-Copyright 2001-2017 The Apache Software Foundation.

+Copyright 2001-2020 The Apache Software Foundation.

 

 This product includes software developed at

 The Apache Software Foundation (http://www.apache.org/). 

diff --git a/README.md b/README.md
index ba2a3cb..ddafd09 100644
--- a/README.md
+++ b/README.md
@@ -61,8 +61,8 @@
 ```xml
 <dependency>
   <groupId>org.apache.commons</groupId>
-  <artifactId>commons-jcs</artifactId>
-  <version>2.0</version>
+  <artifactId>commons-jcs3</artifactId>
+  <version>3.0</version>
 </dependency>
 ```
 
diff --git a/commons-jcs-core/pom.xml b/commons-jcs-core/pom.xml
index 66fc82d..62ef52a 100644
--- a/commons-jcs-core/pom.xml
+++ b/commons-jcs-core/pom.xml
@@ -21,12 +21,12 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <groupId>org.apache.commons</groupId>
-    <artifactId>commons-jcs</artifactId>
+    <artifactId>commons-jcs3</artifactId>
     <version>3.0-SNAPSHOT</version>
     <relativePath>../pom.xml</relativePath>
   </parent>
 
-  <artifactId>commons-jcs-core</artifactId>
+  <artifactId>commons-jcs3-core</artifactId>
   <version>3.0-SNAPSHOT</version>
   <packaging>jar</packaging>
 
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/JCS.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/JCS.java
deleted file mode 100644
index 8a006a2..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/JCS.java
+++ /dev/null
@@ -1,210 +0,0 @@
-package org.apache.commons.jcs;
-
-import java.util.Properties;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.access.GroupCacheAccess;
-import org.apache.commons.jcs.access.exception.CacheException;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheAttributes;
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-import org.apache.commons.jcs.engine.control.CompositeCache;
-import org.apache.commons.jcs.engine.control.CompositeCacheManager;
-import org.apache.commons.jcs.engine.control.group.GroupAttrName;
-
-/**
- * Simple class for using JCS. To use JCS in your application, you can use the static methods of
- * this class to get access objects (instances of this class) for your cache regions. One CacheAccess
- * object should be created for each region you want to access. If you have several regions, then
- * get instances for each. For best performance the getInstance call should be made in an
- * initialization method.
- */
-public abstract class JCS
-{
-    /** cache.ccf alternative. */
-    private static String configFilename = null;
-
-    /** alternative configuration properties */
-    private static Properties configProps = null;
-
-    /** Cache manager use by the various forms of defineRegion and getAccess */
-    private static CompositeCacheManager cacheMgr;
-
-    /**
-     * Set the filename that the cache manager will be initialized with. Only matters before the
-     * instance is initialized.
-     * <p>
-     * @param configFilename
-     */
-    public static void setConfigFilename( String configFilename )
-    {
-        JCS.configFilename = configFilename;
-    }
-
-    /**
-     * Set the properties that the cache manager will be initialized with. Only
-     * matters before the instance is initialized.
-     *
-     * @param configProps
-     */
-    public static void setConfigProperties( Properties configProps )
-    {
-        JCS.configProps = configProps;
-    }
-
-    /**
-     * Shut down the cache manager and set the instance to null
-     */
-    public static void shutdown()
-    {
-        synchronized ( JCS.class )
-        {
-            if ( cacheMgr != null && cacheMgr.isInitialized())
-            {
-            	cacheMgr.shutDown();
-            }
-
-            cacheMgr = null;
-        }
-    }
-
-    /**
-     * Helper method which checks to make sure the cacheMgr class field is set, and if not requests
-     * an instance from CacheManagerFactory.
-     *
-     * @throws CacheException if the configuration cannot be loaded
-     */
-    private static CompositeCacheManager getCacheManager() throws CacheException
-    {
-        synchronized ( JCS.class )
-        {
-            if ( cacheMgr == null || !cacheMgr.isInitialized())
-            {
-                if ( configProps != null )
-                {
-                    cacheMgr = CompositeCacheManager.getUnconfiguredInstance();
-                    cacheMgr.configure( configProps );
-                }
-                else if ( configFilename != null )
-                {
-                    cacheMgr = CompositeCacheManager.getUnconfiguredInstance();
-                    cacheMgr.configure( configFilename );
-                }
-                else
-                {
-                    cacheMgr = CompositeCacheManager.getInstance();
-                }
-            }
-
-            return cacheMgr;
-        }
-    }
-
-    /**
-     * Get a CacheAccess which accesses the provided region.
-     * <p>
-     * @param region Region that return CacheAccess will provide access to
-     * @return A CacheAccess which provides access to a given region.
-     * @throws CacheException
-     */
-    public static <K, V> CacheAccess<K, V> getInstance( String region )
-        throws CacheException
-    {
-        CompositeCache<K, V> cache = getCacheManager().getCache( region );
-        return new CacheAccess<>( cache );
-    }
-
-    /**
-     * Get a CacheAccess which accesses the provided region.
-     * <p>
-     * @param region Region that return CacheAccess will provide access to
-     * @param icca CacheAttributes for region
-     * @return A CacheAccess which provides access to a given region.
-     * @throws CacheException
-     */
-    public static <K, V> CacheAccess<K, V> getInstance( String region, ICompositeCacheAttributes icca )
-        throws CacheException
-    {
-        CompositeCache<K, V> cache = getCacheManager().getCache( region, icca );
-        return new CacheAccess<>( cache );
-    }
-
-    /**
-     * Get a CacheAccess which accesses the provided region.
-     * <p>
-     * @param region Region that return CacheAccess will provide access to
-     * @param icca CacheAttributes for region
-     * @param eattr ElementAttributes for the region
-     * @return A CacheAccess which provides access to a given region.
-     * @throws CacheException
-     */
-    public static <K, V> CacheAccess<K, V> getInstance( String region, ICompositeCacheAttributes icca,  IElementAttributes eattr )
-        throws CacheException
-    {
-        CompositeCache<K, V> cache = getCacheManager().getCache( region, icca, eattr );
-        return new CacheAccess<>( cache );
-    }
-
-    /**
-     * Get a GroupCacheAccess which accesses the provided region.
-     * <p>
-     * @param region Region that return GroupCacheAccess will provide access to
-     * @return A GroupCacheAccess which provides access to a given region.
-     * @throws CacheException
-     */
-    public static <K, V> GroupCacheAccess<K, V> getGroupCacheInstance( String region )
-        throws CacheException
-    {
-        CompositeCache<GroupAttrName<K>, V> cache = getCacheManager().getCache( region );
-        return new GroupCacheAccess<>( cache );
-    }
-
-    /**
-     * Get a GroupCacheAccess which accesses the provided region.
-     * <p>
-     * @param region Region that return GroupCacheAccess will provide access to
-     * @param icca CacheAttributes for region
-     * @return A GroupCacheAccess which provides access to a given region.
-     * @throws CacheException
-     */
-    public static <K, V> GroupCacheAccess<K, V> getGroupCacheInstance( String region, ICompositeCacheAttributes icca )
-        throws CacheException
-    {
-        CompositeCache<GroupAttrName<K>, V> cache = getCacheManager().getCache( region, icca );
-        return new GroupCacheAccess<>( cache );
-    }
-
-    /**
-     * Get a GroupCacheAccess which accesses the provided region.
-     * <p>
-     * @param region Region that return CacheAccess will provide access to
-     * @param icca CacheAttributes for region
-     * @param eattr ElementAttributes for the region
-     * @return A GroupCacheAccess which provides access to a given region.
-     * @throws CacheException
-     */
-    public static <K, V> GroupCacheAccess<K, V> getGroupCacheInstance( String region, ICompositeCacheAttributes icca,  IElementAttributes eattr )
-        throws CacheException
-    {
-        CompositeCache<GroupAttrName<K>, V> cache = getCacheManager().getCache( region, icca, eattr );
-        return new GroupCacheAccess<>( cache );
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/AbstractCacheAccess.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/AbstractCacheAccess.java
deleted file mode 100644
index c727c7c..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/AbstractCacheAccess.java
+++ /dev/null
@@ -1,203 +0,0 @@
-package org.apache.commons.jcs.access;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-
-import org.apache.commons.jcs.access.behavior.ICacheAccessManagement;
-import org.apache.commons.jcs.access.exception.CacheException;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheAttributes;
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-import org.apache.commons.jcs.engine.control.CompositeCache;
-import org.apache.commons.jcs.engine.stats.behavior.ICacheStats;
-
-/**
- * This class provides the common methods for all types of access to the cache.
- * <p>
- * An instance of this class is tied to a specific cache region. Static methods are provided to get
- * such instances.
- * <p>
- * Using this class you can retrieve an item, the item's wrapper, and the element's configuration.  You can also put an
- * item in the cache, remove an item, and clear a region.
- * <p>
- * The JCS class is the preferred way to access these methods.
- */
-public abstract class AbstractCacheAccess<K, V>
-    implements ICacheAccessManagement
-{
-    /**
-     * The cache that a given instance of this class provides access to.
-     * <p>
-     * TODO Should this be the interface?
-     */
-    private final CompositeCache<K, V> cacheControl;
-
-    /**
-     * Constructor for the CacheAccess object.
-     * <p>
-     * @param cacheControl The cache which the created instance accesses
-     */
-    protected AbstractCacheAccess( CompositeCache<K, V> cacheControl )
-    {
-        this.cacheControl = cacheControl;
-    }
-
-    /**
-     * Removes all of the elements from a region.
-     * <p>
-     * @throws CacheException
-     */
-    @Override
-    public void clear()
-        throws CacheException
-    {
-        try
-        {
-            this.getCacheControl().removeAll();
-        }
-        catch ( IOException e )
-        {
-            throw new CacheException( e );
-        }
-    }
-
-    /**
-     * This method is does not reset the attributes for items already in the cache. It could
-     * potentially do this for items in memory, and maybe on disk (which would be slow) but not
-     * remote items. Rather than have unpredictable behavior, this method just sets the default
-     * attributes. Items subsequently put into the cache will use these defaults if they do not
-     * specify specific attributes.
-     * <p>
-     * @param attr the default attributes.
-     * @throws CacheException if something goes wrong.
-     */
-    @Override
-    public void setDefaultElementAttributes( IElementAttributes attr )
-        throws CacheException
-    {
-        this.getCacheControl().setElementAttributes( attr );
-    }
-
-    /**
-     * Retrieves A COPY OF the default element attributes used by this region. This does not provide
-     * a reference to the element attributes.
-     * <p>
-     * Each time an element is added to the cache without element attributes, the default element
-     * attributes are cloned.
-     * <p>
-     * @return the default element attributes used by this region.
-     * @throws CacheException
-     */
-    @Override
-    public IElementAttributes getDefaultElementAttributes()
-        throws CacheException
-    {
-        return this.getCacheControl().getElementAttributes();
-    }
-
-    /**
-     * This returns the ICacheStats object with information on this region and its auxiliaries.
-     * <p>
-     * This data can be formatted as needed.
-     * <p>
-     * @return ICacheStats
-     */
-    @Override
-    public ICacheStats getStatistics()
-    {
-        return this.getCacheControl().getStatistics();
-    }
-
-    /**
-     * @return A String version of the stats.
-     */
-    @Override
-    public String getStats()
-    {
-        return this.getCacheControl().getStats();
-    }
-
-    /**
-     * Dispose this region. Flushes objects to and closes auxiliary caches. This is a shutdown
-     * command!
-     * <p>
-     * To simply remove all elements from the region use clear().
-     */
-    @Override
-    public void dispose()
-    {
-        this.getCacheControl().dispose();
-    }
-
-    /**
-     * Gets the ICompositeCacheAttributes of the cache region.
-     * <p>
-     * @return ICompositeCacheAttributes, the controllers config info, defined in the top section of
-     *         a region definition.
-     */
-    @Override
-    public ICompositeCacheAttributes getCacheAttributes()
-    {
-        return this.getCacheControl().getCacheAttributes();
-    }
-
-    /**
-     * Sets the ICompositeCacheAttributes of the cache region.
-     * <p>
-     * @param cattr The new ICompositeCacheAttribute value
-     */
-    @Override
-    public void setCacheAttributes( ICompositeCacheAttributes cattr )
-    {
-        this.getCacheControl().setCacheAttributes( cattr );
-    }
-
-    /**
-     * This instructs the memory cache to remove the <i>numberToFree</i> according to its eviction
-     * policy. For example, the LRUMemoryCache will remove the <i>numberToFree</i> least recently
-     * used items. These will be spooled to disk if a disk auxiliary is available.
-     * <p>
-     * @param numberToFree
-     * @return the number that were removed. if you ask to free 5, but there are only 3, you will
-     *         get 3.
-     * @throws CacheException
-     */
-    @Override
-    public int freeMemoryElements( int numberToFree )
-        throws CacheException
-    {
-        int numFreed = -1;
-        try
-        {
-            numFreed = this.getCacheControl().getMemoryCache().freeElements( numberToFree );
-        }
-        catch ( IOException ioe )
-        {
-            String message = "Failure freeing memory elements.";
-            throw new CacheException( message, ioe );
-        }
-        return numFreed;
-    }
-
-    public CompositeCache<K, V> getCacheControl() {
-        return cacheControl;
-    }
-
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/CacheAccess.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/CacheAccess.java
deleted file mode 100644
index 90b42c0..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/CacheAccess.java
+++ /dev/null
@@ -1,332 +0,0 @@
-package org.apache.commons.jcs.access;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Supplier;
-import java.util.stream.Collectors;
-
-import org.apache.commons.jcs.access.behavior.ICacheAccess;
-import org.apache.commons.jcs.access.exception.CacheException;
-import org.apache.commons.jcs.access.exception.InvalidArgumentException;
-import org.apache.commons.jcs.access.exception.InvalidHandleException;
-import org.apache.commons.jcs.access.exception.ObjectExistsException;
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-import org.apache.commons.jcs.engine.control.CompositeCache;
-
-/**
- * This class provides an interface for all types of access to the cache.
- * <p>
- * An instance of this class is tied to a specific cache region. Static methods are provided to get
- * such instances.
- * <p>
- * Using this class you can retrieve an item, the item's wrapper, and the element's configuration.  You can also put an
- * item in the cache, remove an item, and clear a region.
- * <p>
- * The JCS class is the preferred way to access these methods.
- */
-public class CacheAccess<K, V>
-    extends AbstractCacheAccess<K, V>
-    implements ICacheAccess<K, V>
-{
-    /**
-     * Constructor for the CacheAccess object.
-     * <p>
-     * @param cacheControl The cache which the created instance accesses
-     */
-    public CacheAccess( CompositeCache<K, V> cacheControl )
-    {
-        super(cacheControl);
-    }
-
-    /**
-     * Retrieve an object from the cache region this instance provides access to.
-     * <p>
-     * @param name Key the object is stored as
-     * @return The object if found or null
-     */
-    @Override
-    public V get( K name )
-    {
-        ICacheElement<K, V> element = this.getCacheControl().get( name );
-
-        return ( element != null ) ? element.getVal() : null;
-    }
-
-    /**
-     * Retrieve an object from the cache region this instance provides access to.
-     * If the object cannot be found in the cache, it will be retrieved by
-     * calling the supplier and subsequently storing it in the cache.
-     * <p>
-     * @param name
-     * @param supplier supplier to be called if the value is not found
-     * @return Object.
-     */
-    @Override
-    public V get(K name, Supplier<V> supplier)
-    {
-        V value = get(name);
-
-        if (value == null)
-        {
-            value = supplier.get();
-            put(name, value);
-        }
-
-        return value;
-    }
-
-    /**
-     * Retrieve matching objects from the cache region this instance provides access to.
-     * <p>
-     * @param pattern - a key pattern for the objects stored
-     * @return A map of key to values.  These are stripped from the wrapper.
-     */
-    @Override
-    public Map<K, V> getMatching( String pattern )
-    {
-        Map<K, V> unwrappedResults;
-
-        Map<K, ICacheElement<K, V>> wrappedResults = this.getCacheControl().getMatching( pattern );
-
-        if ( wrappedResults == null )
-        {
-            unwrappedResults = new HashMap<>();
-        }
-        else
-        {
-            unwrappedResults = wrappedResults.entrySet()
-                    .stream()
-                    .filter(entry -> entry.getValue() != null)
-                    .collect(Collectors.toMap(
-                            entry -> entry.getKey(),
-                            entry -> entry.getValue().getVal()));
-        }
-
-        return unwrappedResults;
-    }
-
-    /**
-     * This method returns the ICacheElement&lt;K, V&gt; wrapper which provides access to element info and other
-     * attributes.
-     * <p>
-     * This returns a reference to the wrapper. Any modifications will be reflected in the cache. No
-     * defensive copy is made.
-     * <p>
-     * This method is most useful if you want to determine things such as the how long the element
-     * has been in the cache.
-     * <p>
-     * The last access time in the ElementAttributes should be current.
-     * <p>
-     * @param name Key the Serializable is stored as
-     * @return The ICacheElement&lt;K, V&gt; if the object is found or null
-     */
-    @Override
-    public ICacheElement<K, V> getCacheElement( K name )
-    {
-        return this.getCacheControl().get( name );
-    }
-
-    /**
-     * Get multiple elements from the cache based on a set of cache keys.
-     * <p>
-     * This method returns the ICacheElement&lt;K, V&gt; wrapper which provides access to element info and other
-     * attributes.
-     * <p>
-     * This returns a reference to the wrapper. Any modifications will be reflected in the cache. No
-     * defensive copy is made.
-     * <p>
-     * This method is most useful if you want to determine things such as the how long the element
-     * has been in the cache.
-     * <p>
-     * The last access time in the ElementAttributes should be current.
-     * <p>
-     * @param names set of Serializable cache keys
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or empty map if none of the keys are present
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getCacheElements( Set<K> names )
-    {
-        return this.getCacheControl().getMultiple( names );
-    }
-
-    /**
-     * Get multiple elements from the cache based on a set of cache keys.
-     * <p>
-     * This method returns the ICacheElement&lt;K, V&gt; wrapper which provides access to element info and other
-     * attributes.
-     * <p>
-     * This returns a reference to the wrapper. Any modifications will be reflected in the cache. No
-     * defensive copy is made.
-     * <p>
-     * This method is most useful if you want to determine things such as the how long the element
-     * has been in the cache.
-     * <p>
-     * The last access time in the ElementAttributes should be current.
-     * <p>
-     * @param pattern key search pattern
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or empty map if no keys match the pattern
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMatchingCacheElements( String pattern )
-    {
-        return this.getCacheControl().getMatching( pattern );
-    }
-
-    /**
-     * Place a new object in the cache, associated with key name. If there is currently an object
-     * associated with name in the region an ObjectExistsException is thrown. Names are scoped to a
-     * region so they must be unique within the region they are placed.
-     * <p>
-     * @param key Key object will be stored with
-     * @param value Object to store
-     * @throws CacheException and ObjectExistsException is thrown if the item is already in the
-     *                cache.
-     */
-    @Override
-    public void putSafe( K key, V value )
-    {
-        if ( this.getCacheControl().get( key ) != null )
-        {
-            throw new ObjectExistsException( "putSafe failed.  Object exists in the cache for key [" + key
-                + "].  Remove first or use a non-safe put to override the value." );
-        }
-        put( key, value );
-    }
-
-    /**
-     * Place a new object in the cache, associated with key name. If there is currently an object
-     * associated with name in the region it is replaced. Names are scoped to a region so they must
-     * be unique within the region they are placed.
-     * @param name Key object will be stored with
-     * @param obj Object to store
-     */
-    @Override
-    public void put( K name, V obj )
-    {
-        // Call put with a copy of the contained caches default attributes.
-        // the attributes are copied by the cacheControl
-        put( name, obj, this.getCacheControl().getElementAttributes() );
-    }
-
-    /**
-     * Constructs a cache element with these attributes, and puts it into the cache.
-     * <p>
-     * If the key or the value is null, and InvalidArgumentException is thrown.
-     * <p>
-     * @see org.apache.commons.jcs.access.behavior.ICacheAccess#put(Object, Object, IElementAttributes)
-     */
-    @Override
-    public void put( K key, V val, IElementAttributes attr )
-    {
-        if ( key == null )
-        {
-            throw new InvalidArgumentException( "Key must not be null" );
-        }
-
-        if ( val == null )
-        {
-            throw new InvalidArgumentException( "Value must not be null" );
-        }
-
-        // Create the element and update. This may throw an IOException which
-        // should be wrapped by cache access.
-        try
-        {
-            CacheElement<K, V> ce = new CacheElement<>( this.getCacheControl().getCacheName(), key,
-                                                val );
-
-            ce.setElementAttributes( attr );
-
-            this.getCacheControl().update( ce );
-        }
-        catch ( IOException e )
-        {
-            throw new CacheException( e );
-        }
-    }
-
-    /**
-     * Removes a single item by name.
-     * <p>
-     * @param name the name of the item to remove.
-     */
-    @Override
-    public void remove( K name )
-    {
-        this.getCacheControl().remove( name );
-    }
-
-    /**
-     * Reset attributes for a particular element in the cache. NOTE: this method is currently not
-     * implemented.
-     * <p>
-     * @param name Key of object to reset attributes for
-     * @param attr New attributes for the object
-     * @throws InvalidHandleException if the item does not exist.
-     */
-    @Override
-    public void resetElementAttributes( K name, IElementAttributes attr )
-    {
-        ICacheElement<K, V> element = this.getCacheControl().get( name );
-
-        if ( element == null )
-        {
-            throw new InvalidHandleException( "Object for name [" + name + "] is not in the cache" );
-        }
-
-        // Although it will work currently, don't assume pass by reference here,
-        // i.e. don't do this:
-        // element.setElementAttributes( attr );
-        // Another reason to call put is to force the changes to be distributed.
-
-        put( element.getKey(), element.getVal(), attr );
-    }
-
-    /**
-     * GetElementAttributes will return an attribute object describing the current attributes
-     * associated with the object name. The name object must override the Object.equals and
-     * Object.hashCode methods.
-     * <p>
-     * @param name Key of object to get attributes for
-     * @return Attributes for the object, null if object not in cache
-     */
-    @Override
-    public IElementAttributes getElementAttributes( K name ) throws CacheException
-    {
-        IElementAttributes attr = null;
-
-        try
-        {
-            attr = this.getCacheControl().getElementAttributes( name );
-        }
-        catch ( IOException ioe )
-        {
-            throw new CacheException("Failure getting element attributes", ioe);
-        }
-
-        return attr;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/GroupCacheAccess.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/GroupCacheAccess.java
deleted file mode 100644
index d98fe1a..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/GroupCacheAccess.java
+++ /dev/null
@@ -1,206 +0,0 @@
-package org.apache.commons.jcs.access;
-
-import java.io.IOException;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.access.behavior.IGroupCacheAccess;
-import org.apache.commons.jcs.access.exception.CacheException;
-import org.apache.commons.jcs.access.exception.InvalidArgumentException;
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-import org.apache.commons.jcs.engine.control.CompositeCache;
-import org.apache.commons.jcs.engine.control.group.GroupAttrName;
-import org.apache.commons.jcs.engine.control.group.GroupId;
-
-/**
- * Access for groups.
- */
-public class GroupCacheAccess<K, V>
-    extends AbstractCacheAccess<GroupAttrName<K>, V>
-    implements IGroupCacheAccess<K, V>
-{
-    /**
-     * Constructor for the GroupCacheAccess object
-     * <p>
-     * @param cacheControl
-     */
-    public GroupCacheAccess( CompositeCache<GroupAttrName<K>, V> cacheControl )
-    {
-        super(cacheControl);
-    }
-
-    /**
-     * Gets an item out of the cache that is in a specified group.
-     * <p>
-     * @param name
-     *            The key name.
-     * @param group
-     *            The group name.
-     * @return The cached value, null if not found.
-     */
-    @Override
-    public V getFromGroup( K name, String group )
-    {
-        ICacheElement<GroupAttrName<K>, V> element = this.getCacheControl().get( getGroupAttrName( group, name ) );
-        return ( element != null ) ? element.getVal() : null;
-    }
-
-    /**
-     * Internal method used for group functionality.
-     * <p>
-     * @param group
-     * @param name
-     * @return GroupAttrName
-     */
-    private GroupAttrName<K> getGroupAttrName( String group, K name )
-    {
-        GroupId gid = new GroupId( this.getCacheControl().getCacheName(), group );
-        return new GroupAttrName<>( gid, name );
-    }
-
-    /**
-     * Allows the user to put an object into a group within a particular cache
-     * region. This method sets the object's attributes to the default for the
-     * region.
-     * <p>
-     * @param name
-     *            The key name.
-     * @param groupName
-     *            The group name.
-     * @param value
-     *            The object to cache
-     * @throws CacheException
-     */
-    @Override
-    public void putInGroup( K name, String groupName, V value )
-        throws CacheException
-    {
-        putInGroup( name, groupName, value, null );
-    }
-
-    /**
-     * Allows the user to put an object into a group within a particular cache
-     * region. This method allows the object's attributes to be individually
-     * specified.
-     * <p>
-     * @param name
-     *            The key name.
-     * @param groupName
-     *            The group name.
-     * @param value
-     *            The object to cache
-     * @param attr
-     *            The objects attributes.
-     * @throws CacheException
-     */
-    @Override
-    public void putInGroup( K name, String groupName, V value, IElementAttributes attr )
-        throws CacheException
-    {
-        if ( name == null )
-        {
-            throw new InvalidArgumentException( "Key must not be null" );
-        }
-
-        if ( value == null )
-        {
-            throw new InvalidArgumentException( "Value must not be null" );
-        }
-
-        // Create the element and update. This may throw an IOException which
-        // should be wrapped by cache access.
-        try
-        {
-            GroupAttrName<K> key = getGroupAttrName( groupName, name );
-            CacheElement<GroupAttrName<K>, V> ce =
-                new CacheElement<>( this.getCacheControl().getCacheName(), key, value );
-
-            IElementAttributes attributes = (attr == null) ? this.getCacheControl().getElementAttributes() : attr;
-            ce.setElementAttributes( attributes );
-
-            this.getCacheControl().update( ce );
-        }
-        catch ( IOException e )
-        {
-            throw new CacheException( e );
-        }
-
-    }
-
-    /**
-     * Removes a single item by name from a group.
-     *
-     * @param name
-     * @param group
-     */
-    @Override
-    public void removeFromGroup( K name, String group )
-    {
-        GroupAttrName<K> key = getGroupAttrName( group, name );
-        this.getCacheControl().remove( key );
-    }
-
-    /**
-     * Gets the set of keys of objects currently in the group.
-     * <p>
-     * @param group
-     * @return A Set of keys.
-     */
-    @Override
-    public Set<K> getGroupKeys( String group )
-    {
-        GroupId groupId = new GroupId( this.getCacheControl().getCacheName(), group );
-
-        return this.getCacheControl().getKeySet()
-                .stream()
-                .filter(gan -> gan.groupId.equals(groupId))
-                .map(gan -> gan.attrName)
-                .collect(Collectors.toSet());
-    }
-
-    /**
-     * Gets the set of group names in the cache
-     * <p>
-     * @return A Set of group names.
-     */
-    public Set<String> getGroupNames()
-    {
-        return this.getCacheControl().getKeySet()
-                .stream()
-                .map(gan -> gan.groupId.groupName)
-                .collect(Collectors.toSet());
-    }
-
-    /**
-     * Invalidates a group: remove all the group members
-     * <p>
-     * @param group
-     *            The name of the group to invalidate
-     */
-    @Override
-    public void invalidateGroup( String group )
-    {
-        this.getCacheControl().remove(getGroupAttrName(group, null));
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/behavior/ICacheAccess.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/behavior/ICacheAccess.java
deleted file mode 100644
index f48abdd..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/behavior/ICacheAccess.java
+++ /dev/null
@@ -1,178 +0,0 @@
-package org.apache.commons.jcs.access.behavior;
-
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Supplier;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.access.exception.CacheException;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-
-/**
- * ICacheAccess defines the behavior for client access.
- */
-public interface ICacheAccess<K, V>
-    extends ICacheAccessManagement
-{
-    /**
-     * Basic get method.
-     * <p>
-     * @param name
-     * @return Object or null if not found.
-     */
-    V get(K name);
-
-    /**
-     * Basic get method. If the object cannot be found in the cache, it will be
-     * retrieved by calling the supplier and subsequently storing it in the cache.
-     * <p>
-     * @param name
-     * @param supplier supplier to be called if the value is not found
-     * @return Object.
-     */
-    V get(K name, Supplier<V> supplier);
-
-    /**
-     * Retrieve matching objects from the cache region this instance provides access to.
-     * <p>
-     * @param pattern - a key pattern for the objects stored
-     * @return A map of key to values. These are stripped from the wrapper.
-     */
-    Map<K, V> getMatching(String pattern);
-
-    /**
-     * Puts in cache if an item does not exist with the name in that region.
-     * <p>
-     * @param name
-     * @param obj
-     * @throws CacheException
-     */
-    void putSafe(K name, V obj)
-        throws CacheException;
-
-    /**
-     * Puts and/or overrides an element with the name in that region.
-     * <p>
-     * @param name
-     * @param obj
-     * @throws CacheException
-     */
-    void put(K name, V obj)
-        throws CacheException;
-
-    /**
-     * Description of the Method
-     * <p>
-     * @param name
-     * @param obj
-     * @param attr
-     * @throws CacheException
-     */
-    void put(K name, V obj, IElementAttributes attr)
-        throws CacheException;
-
-    /**
-     * This method returns the ICacheElement&lt;K, V&gt; wrapper which provides access to element info and other
-     * attributes.
-     * <p>
-     * This returns a reference to the wrapper. Any modifications will be reflected in the cache. No
-     * defensive copy is made.
-     * <p>
-     * This method is most useful if you want to determine things such as the how long the element
-     * has been in the cache.
-     * <p>
-     * The last access time in the ElementAttributes should be current.
-     * <p>
-     * @param name Key the object is stored as
-     * @return The ICacheElement&lt;K, V&gt; if the object is found or null
-     */
-    ICacheElement<K, V> getCacheElement(K name);
-
-    /**
-     * Get multiple elements from the cache based on a set of cache keys.
-     * <p>
-     * This method returns the ICacheElement&lt;K, V&gt; wrapper which provides access to element info and other
-     * attributes.
-     * <p>
-     * This returns a reference to the wrapper. Any modifications will be reflected in the cache. No
-     * defensive copy is made.
-     * <p>
-     * This method is most useful if you want to determine things such as the how long the element
-     * has been in the cache.
-     * <p>
-     * The last access time in the ElementAttributes should be current.
-     * <p>
-     * @param names set of Object cache keys
-     * @return a map of Object key to ICacheElement&lt;K, V&gt; element, or empty map if none of the keys are
-     *         present
-     */
-    Map<K, ICacheElement<K, V>> getCacheElements(Set<K> names);
-
-    /**
-     * Get multiple elements from the cache based on a set of cache keys.
-     * <p>
-     * This method returns the ICacheElement&lt;K, V&gt; wrapper which provides access to element info and other
-     * attributes.
-     * <p>
-     * This returns a reference to the wrapper. Any modifications will be reflected in the cache. No
-     * defensive copy is made.
-     * <p>
-     * This method is most useful if you want to determine things such as the how long the element
-     * has been in the cache.
-     * <p>
-     * The last access time in the ElementAttributes should be current.
-     * <p>
-     * @param pattern key search pattern
-     * @return a map of Object key to ICacheElement&lt;K, V&gt; element, or empty map if no keys match the
-     *         pattern
-     */
-    Map<K, ICacheElement<K, V>> getMatchingCacheElements(String pattern);
-
-    /**
-     * Remove an object for this key if one exists, else do nothing.
-     * <p>
-     * @param name
-     * @throws CacheException
-     */
-    void remove(K name)
-        throws CacheException;
-
-    /**
-     * Reset the attributes on the object matching this key name.
-     * <p>
-     * @param name
-     * @param attributes
-     * @throws CacheException
-     */
-    void resetElementAttributes(K name, IElementAttributes attributes)
-        throws CacheException;
-
-    /**
-     * Gets the elementAttributes attribute of the ICacheAccess object
-     * <p>
-     * @param name
-     * @return The elementAttributes value
-     * @throws CacheException
-     */
-    IElementAttributes getElementAttributes(K name)
-        throws CacheException;
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/behavior/ICacheAccessManagement.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/behavior/ICacheAccessManagement.java
deleted file mode 100644
index 21c01f4..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/behavior/ICacheAccessManagement.java
+++ /dev/null
@@ -1,111 +0,0 @@
-package org.apache.commons.jcs.access.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.access.exception.CacheException;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheAttributes;
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-import org.apache.commons.jcs.engine.stats.behavior.ICacheStats;
-
-/**
- * ICacheAccessManagement defines the methods for cache management, cleanup and shutdown.
- */
-public interface ICacheAccessManagement
-{
-    /**
-     * Dispose this region. Flushes objects to and closes auxiliary caches. This is a shutdown
-     * command!
-     * <p>
-     * To simply remove all elements from the region use clear().
-     */
-    void dispose();
-
-    /**
-     * Removes all of the elements from a region.
-     * <p>
-     * @throws CacheException
-     */
-    void clear() throws CacheException;
-
-    /**
-     * GetElementAttributes will return an attribute object describing the current attributes
-     * associated with the object name. If no name parameter is available, the attributes for the
-     * region will be returned. The name object must override the Object.equals and Object.hashCode
-     * methods.
-     * <p>
-     * @return The elementAttributes value
-     * @throws CacheException
-     */
-    IElementAttributes getDefaultElementAttributes()
-        throws CacheException;
-
-    /**
-     * This method is does not reset the attributes for items already in the cache. It could
-     * potentially do this for items in memory, and maybe on disk (which would be slow) but not
-     * remote items. Rather than have unpredictable behavior, this method just sets the default
-     * attributes. Items subsequently put into the cache will use these defaults if they do not
-     * specify specific attributes.
-     * <p>
-     * @param attr the default attributes.
-     * @throws CacheException if something goes wrong.
-     */
-    void setDefaultElementAttributes( IElementAttributes attr ) throws CacheException;
-
-    /**
-     * Gets the ICompositeCacheAttributes of the cache region
-     * <p>
-     * @return ICompositeCacheAttributes
-     */
-    ICompositeCacheAttributes getCacheAttributes();
-
-    /**
-     * Sets the ICompositeCacheAttributes of the cache region
-     * <p>
-     * @param cattr The new ICompositeCacheAttribute value
-     */
-    void setCacheAttributes( ICompositeCacheAttributes cattr );
-
-    /**
-     * This instructs the memory cache to remove the <i>numberToFree</i> according to its eviction
-     * policy. For example, the LRUMemoryCache will remove the <i>numberToFree</i> least recently
-     * used items. These will be spooled to disk if a disk auxiliary is available.
-     * <p>
-     * @param numberToFree
-     * @return the number that were removed. if you ask to free 5, but there are only 3, you will
-     *         get 3.
-     * @throws CacheException
-     */
-    int freeMemoryElements( int numberToFree )
-        throws CacheException;
-
-    /**
-     * This returns the ICacheStats object with information on this region and its auxiliaries.
-     * <p>
-     * This data can be formatted as needed.
-     * <p>
-     * @return ICacheStats
-     */
-    ICacheStats getStatistics();
-
-    /**
-     * @return A String version of the stats.
-     */
-    String getStats();
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/behavior/IGroupCacheAccess.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/behavior/IGroupCacheAccess.java
deleted file mode 100644
index 4527d24..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/behavior/IGroupCacheAccess.java
+++ /dev/null
@@ -1,89 +0,0 @@
-package org.apache.commons.jcs.access.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.access.exception.CacheException;
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-
-import java.util.Set;
-
-/**
- * IGroupCacheAccess defines group specific behavior for the client access
- * classes.
- */
-public interface IGroupCacheAccess<K, V>
-    extends ICacheAccessManagement
-{
-    /**
-     * Gets the g attribute of the IGroupCacheAccess object
-     * <p>
-     * @param name
-     * @param group
-     *            the name of the group to associate this with.
-     * @return The object that is keyed by the name in the group
-     */
-    V getFromGroup( K name, String group );
-
-    /**
-     * Puts an item in the cache associated with this group.
-     * <p>
-     * @param key
-     * @param group
-     * @param obj
-     * @throws CacheException
-     */
-    void putInGroup( K key, String group, V obj )
-        throws CacheException;
-
-    /**
-     * Put in the cache associated with this group using these attributes.
-     * <p>
-     * @param key
-     * @param group
-     * @param obj
-     * @param attr
-     * @throws CacheException
-     */
-    void putInGroup( K key, String group, V obj, IElementAttributes attr )
-        throws CacheException;
-
-    /**
-     * Remove the item from this group in this region by this name.
-     * <p>
-     * @param name
-     * @param group
-     */
-    void removeFromGroup( K name, String group );
-
-    /**
-     * Gets the set of keys of objects currently in the group
-     * <p>
-     * @param group
-     * @return the set of group keys.
-     */
-    Set<K> getGroupKeys( String group );
-
-    /**
-     * Invalidates a group
-     * <p>
-     * @param group
-     */
-    void invalidateGroup( String group );
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/exception/CacheException.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/exception/CacheException.java
deleted file mode 100644
index 78479b1..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/exception/CacheException.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package org.apache.commons.jcs.access.exception;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * This is the most general exception the cache throws.
- */
-public class CacheException
-    extends RuntimeException
-{
-    /** Don't change. */
-    private static final long serialVersionUID = 8725795372935590265L;
-
-    /**
-     * Default
-     */
-    public CacheException()
-    {
-        super();
-    }
-
-    /**
-     * Constructor for the CacheException object
-     * @param nested a nested exception
-     */
-    public CacheException( Throwable nested )
-    {
-        super(nested);
-    }
-
-    /**
-     * Constructor for the CacheException object
-     * @param message the exception message
-     */
-    public CacheException( String message )
-    {
-        super(message);
-    }
-
-    /**
-     * Constructor for the CacheException object
-     * @param message the exception message
-     * @param nested a nested exception
-     */
-    public CacheException(String message, Throwable nested)
-    {
-        super(message, nested);
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/exception/ConfigurationException.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/exception/ConfigurationException.java
deleted file mode 100644
index 03f4890..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/exception/ConfigurationException.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package org.apache.commons.jcs.access.exception;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/** Thrown if there is some severe configuration problem that makes the cache nonfunctional. */
-public class ConfigurationException
-    extends CacheException
-{
-    /** Don't change. */
-    private static final long serialVersionUID = 6881044536186097055L;
-
-    /** Constructor for the ConfigurationException object */
-    public ConfigurationException()
-    {
-        super();
-    }
-
-    /**
-     * Constructor for the ConfigurationException object.
-     * <p>
-     * @param message
-     */
-    public ConfigurationException( String message )
-    {
-        super( message );
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/exception/InvalidArgumentException.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/exception/InvalidArgumentException.java
deleted file mode 100644
index cfb4435..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/exception/InvalidArgumentException.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package org.apache.commons.jcs.access.exception;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * InvalidArgumentException is thrown if an argument is passed to the cache that is invalid. For
- * instance, null values passed to put result in this exception.
- */
-public class InvalidArgumentException
-    extends CacheException
-{
-    /** Don't change. */
-    private static final long serialVersionUID = -6058373692208755562L;
-
-    /** Constructor for the InvalidArgumentException object */
-    public InvalidArgumentException()
-    {
-        super();
-    }
-
-    /**
-     * Constructor for the InvalidArgumentException object.
-     * <p>
-     * @param message
-     */
-    public InvalidArgumentException( String message )
-    {
-        super( message );
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/exception/InvalidGroupException.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/exception/InvalidGroupException.java
deleted file mode 100644
index 870a402..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/exception/InvalidGroupException.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package org.apache.commons.jcs.access.exception;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * InvalidGroupException
- */
-public class InvalidGroupException
-    extends CacheException
-{
-    /** Don't change. */
-    private static final long serialVersionUID = -5219807114008843480L;
-
-    /** Constructor for the InvalidGroupException object */
-    public InvalidGroupException()
-    {
-        super();
-    }
-
-    /**
-     * Constructor for the InvalidGroupException object
-     * <p>
-     * @param message
-     */
-    public InvalidGroupException( String message )
-    {
-        super( message );
-    }
-
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/exception/InvalidHandleException.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/exception/InvalidHandleException.java
deleted file mode 100644
index 6b0e6d3..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/exception/InvalidHandleException.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package org.apache.commons.jcs.access.exception;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * InvalidHandleException is not used.
- */
-public class InvalidHandleException
-    extends CacheException
-{
-    /** Don't change. */
-    private static final long serialVersionUID = -5947822454839845924L;
-
-    /** Constructor for the InvalidHandleException object */
-    public InvalidHandleException()
-    {
-        // nothing
-        super();
-    }
-
-    /**
-     * Constructor for the InvalidHandleException object.
-     * <p>
-     * @param message
-     */
-    public InvalidHandleException( String message )
-    {
-        super( message );
-    }
-
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/exception/ObjectExistsException.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/exception/ObjectExistsException.java
deleted file mode 100644
index edef1a4..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/exception/ObjectExistsException.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package org.apache.commons.jcs.access.exception;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * The putSafe method on the JCS convenience class throws this exception if the object is already
- * present in the cache.
- * <p>
- * I'm removing this exception from normal use.
- * <p>
- * The overhead of throwing exceptions and the cumbersomeness of coding around exceptions warrants
- * removal. Exceptions like this don't make sense to throw in the course of normal operations to
- * signify a normal and expected condition. Returning null if an object isn't found is sufficient.
- */
-public class ObjectExistsException
-    extends CacheException
-{
-    /** Don't change. */
-    private static final long serialVersionUID = -3779745827993383872L;
-
-    /** Constructor for the ObjectExistsException object */
-    public ObjectExistsException()
-    {
-        super();
-    }
-
-    /**
-     * Constructor for the ObjectExistsException object
-     * @param message
-     */
-    public ObjectExistsException( String message )
-    {
-        super( message );
-    }
-
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/exception/ObjectNotFoundException.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/exception/ObjectNotFoundException.java
deleted file mode 100644
index 8dca2de..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/exception/ObjectNotFoundException.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package org.apache.commons.jcs.access.exception;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * ObjectNotFoundException
- * <p>
- * TODO see if we can remove this.
- * <p>
- * This is thrown from the composite cache if you as for the element attributes and the element does
- * not exist.
- */
-public class ObjectNotFoundException
-    extends CacheException
-{
-    /** Don't change. */
-    private static final long serialVersionUID = 5684353421076546842L;
-
-    /** Constructor for the ObjectNotFoundException object */
-    public ObjectNotFoundException()
-    {
-        super();
-    }
-
-    /**
-     * Constructor for the ObjectNotFoundException object
-     * @param message
-     */
-    public ObjectNotFoundException( String message )
-    {
-        super( message );
-    }
-
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/admin/CacheElementInfo.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/admin/CacheElementInfo.java
deleted file mode 100644
index 1c09423..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/admin/CacheElementInfo.java
+++ /dev/null
@@ -1,124 +0,0 @@
-package org.apache.commons.jcs.admin;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.beans.ConstructorProperties;
-
-
-/**
- * Stores info on a cache element for the template
- */
-public class CacheElementInfo
-{
-    /** element key */
-    private final String key;
-
-    /** is it eternal */
-    private final boolean eternal;
-
-    /** when it was created */
-    private final String createTime;
-
-    /** max life */
-    private final long maxLifeSeconds;
-
-    /** when it will expire */
-    private final long expiresInSeconds;
-
-    /**
-     * Parameterized constructor
-     *
-	 * @param key element key
-	 * @param eternal is it eternal
-	 * @param createTime when it was created
-	 * @param maxLifeSeconds max life
-	 * @param expiresInSeconds when it will expire
-	 */
-    @ConstructorProperties({"key", "eternal", "createTime", "maxLifeSeconds", "expiresInSeconds"})
-    public CacheElementInfo(String key, boolean eternal, String createTime,
-			long maxLifeSeconds, long expiresInSeconds)
-    {
-		super();
-		this.key = key;
-		this.eternal = eternal;
-		this.createTime = createTime;
-		this.maxLifeSeconds = maxLifeSeconds;
-		this.expiresInSeconds = expiresInSeconds;
-	}
-
-	/**
-     * @return a string representation of the key
-     */
-    public String getKey()
-    {
-        return this.key;
-    }
-
-    /**
-     * @return true if the item does not expire
-     */
-    public boolean isEternal()
-    {
-        return this.eternal;
-    }
-
-    /**
-     * @return the time the object was created
-     */
-    public String getCreateTime()
-    {
-        return this.createTime;
-    }
-
-    /**
-     * Ignored if isEternal
-     * @return the longest this object can live.
-     */
-    public long getMaxLifeSeconds()
-    {
-        return this.maxLifeSeconds;
-    }
-
-    /**
-     * Ignored if isEternal
-     * @return how many seconds until this object expires.
-     */
-    public long getExpiresInSeconds()
-    {
-        return this.expiresInSeconds;
-    }
-
-    /**
-     * @return string info on the item
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder buf = new StringBuilder();
-        buf.append( "\nCacheElementInfo " );
-        buf.append( "\n Key [" ).append( getKey() ).append( "]" );
-        buf.append( "\n Eternal [" ).append( isEternal() ).append( "]" );
-        buf.append( "\n CreateTime [" ).append( getCreateTime() ).append( "]" );
-        buf.append( "\n MaxLifeSeconds [" ).append( getMaxLifeSeconds() ).append( "]" );
-        buf.append( "\n ExpiresInSeconds [" ).append( getExpiresInSeconds() ).append( "]" );
-
-        return buf.toString();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/admin/CacheRegionInfo.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/admin/CacheRegionInfo.java
deleted file mode 100644
index 520d38e..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/admin/CacheRegionInfo.java
+++ /dev/null
@@ -1,180 +0,0 @@
-package org.apache.commons.jcs.admin;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.beans.ConstructorProperties;
-
-
-
-/**
- * Stores info on a cache region for the template
- */
-public class CacheRegionInfo
-{
-    /** The name of the cache region */
-    private final String cacheName;
-
-    /** The size of the cache region */
-    private final int cacheSize;
-
-    /** The status of the cache region */
-    private final String cacheStatus;
-
-    /** The statistics of the cache region */
-    private final String cacheStatistics;
-
-    /** The number of memory hits in the cache region */
-    private final long hitCountRam;
-
-    /** The number of auxiliary hits in the cache region */
-    private final long hitCountAux;
-
-    /** The number of misses in the cache region because the items were not found */
-    private final long missCountNotFound;
-
-    /** The number of misses in the cache region because the items were expired */
-    private final long missCountExpired;
-
-    /** The number of bytes counted so far, will be a total of all items */
-    private final long byteCount;
-
-    /**
-     * Parameterized constructor
-     *
-	 * @param cacheName The name of the cache region
-	 * @param cacheSize The size of the cache region
-	 * @param cacheStatus The status of the cache region
-	 * @param cacheStatistics The statistics of the cache region
-	 * @param hitCountRam The number of memory hits in the cache region
-	 * @param hitCountAux The number of auxiliary hits in the cache region
-	 * @param missCountNotFound The number of misses in the cache region because the items were not found
-	 * @param missCountExpired The number of misses in the cache region because the items were expired
-	 * @param byteCount The number of bytes counted so far, will be a total of all items
-	 */
-    @ConstructorProperties({"cacheName", "cacheSize", "cacheStatus", "cacheStatistics",
-    	"hitCountRam", "hitCountAux", "missCountNotFound", "missCountExpired", "byteCount"})
-	public CacheRegionInfo(String cacheName, int cacheSize, String cacheStatus,
-			String cacheStatistics, long hitCountRam, long hitCountAux,
-			long missCountNotFound, long missCountExpired, long byteCount)
-	{
-		super();
-		this.cacheName = cacheName;
-		this.cacheSize = cacheSize;
-		this.cacheStatus = cacheStatus;
-		this.cacheStatistics = cacheStatistics;
-		this.hitCountRam = hitCountRam;
-		this.hitCountAux = hitCountAux;
-		this.missCountNotFound = missCountNotFound;
-		this.missCountExpired = missCountExpired;
-		this.byteCount = byteCount;
-	}
-
-	/**
-	 * @return the cacheName
-	 */
-	public String getCacheName()
-	{
-		return this.cacheName;
-	}
-
-	/**
-	 * @return the cacheSize
-	 */
-	public int getCacheSize()
-	{
-		return this.cacheSize;
-	}
-
-	/**
-     * @return a status string
-     */
-    public String getCacheStatus()
-    {
-        return this.cacheStatus;
-    }
-
-    /**
-     * Return the statistics for the region.
-     * <p>
-     * @return String
-     */
-    public String getCacheStatistics()
-    {
-        return this.cacheStatistics;
-    }
-
-    /**
-	 * @return the hitCountRam
-	 */
-	public long getHitCountRam()
-	{
-		return hitCountRam;
-	}
-
-	/**
-	 * @return the hitCountAux
-	 */
-	public long getHitCountAux()
-	{
-		return hitCountAux;
-	}
-
-	/**
-	 * @return the missCountNotFound
-	 */
-	public long getMissCountNotFound()
-	{
-		return missCountNotFound;
-	}
-
-	/**
-	 * @return the missCountExpired
-	 */
-	public long getMissCountExpired()
-	{
-		return missCountExpired;
-	}
-
-	/**
-     * @return total byte count
-     */
-    public long getByteCount()
-    {
-        return this.byteCount;
-    }
-
-    /**
-     * @return string info on the region
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder buf = new StringBuilder();
-        buf.append( "\nCacheRegionInfo " );
-        if ( cacheName != null )
-        {
-            buf.append( "\n CacheName [" + cacheName + "]" );
-            buf.append( "\n Status [" + cacheStatus + "]" );
-        }
-        buf.append( "\n ByteCount [" + getByteCount() + "]" );
-
-        return buf.toString();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/admin/CountingOnlyOutputStream.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/admin/CountingOnlyOutputStream.java
deleted file mode 100644
index ea0baf8..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/admin/CountingOnlyOutputStream.java
+++ /dev/null
@@ -1,84 +0,0 @@
-package org.apache.commons.jcs.admin;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.io.OutputStream;
-
-/**
- * Keeps track of the number of bytes written to it, but doesn't write them anywhere.
- */
-public class CountingOnlyOutputStream
-    extends OutputStream
-{
-    /** number of bytes passed through */
-    private int count; // TODO should this be long?
-
-    /**
-     * count as we write.
-     * <p>
-     * @param b
-     * @throws IOException
-     */
-    @Override
-    public void write( byte[] b )
-        throws IOException
-    {
-        this.count += b.length;
-    }
-
-    /**
-     * count as we write.
-     * <p>
-     * @param b
-     * @param off
-     * @param len
-     * @throws IOException
-     */
-    @Override
-    public void write( byte[] b, int off, int len )
-        throws IOException
-    {
-        this.count += len;
-    }
-
-    /**
-     * count as we write.
-     * <p>
-     * @param b
-     * @throws IOException
-     */
-    @Override
-    public void write( int b )
-        throws IOException
-    {
-        this.count++;
-    }
-
-    /**
-     * The number of bytes that have passed through this stream.
-     * <p>
-     * @return int
-     */
-    public int getCount()
-    {
-        return this.count;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/admin/JCSAdmin.jsp b/commons-jcs-core/src/main/java/org/apache/commons/jcs/admin/JCSAdmin.jsp
deleted file mode 100644
index d92b0af..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/admin/JCSAdmin.jsp
+++ /dev/null
@@ -1,310 +0,0 @@
-<%--
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements.  See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership.  The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License.  You may obtain a copy of the License at
-
-   http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied.  See the License for the
- specific language governing permissions and limitations
- under the License.
---%>
-<%@page import="org.apache.commons.jcs.JCS"%>
-<%@page import="org.apache.commons.jcs.access.CacheAccess" %>
-<%@page import="org.apache.commons.jcs.admin.CacheElementInfo" %>
-<%@page import="org.apache.commons.jcs.admin.CacheRegionInfo" %>
-<%@page import="java.io.Serializable" %>
-<%@page import="java.util.HashMap" %>
-
-<jsp:useBean id="jcsBean" scope="request" class="org.apache.commons.jcs.admin.JCSAdminBean" />
-
-<html>
-<head>
-
-<SCRIPT LANGUAGE="Javascript">
-  function decision( message, url )
-  {
-    if( confirm(message) )
-    {
-      location.href = url;
-    }
-  }
-</SCRIPT>
-
-<title> JCS Admin Servlet </title>
-
-</head>
-
-<body>
-
-<%
-			String CACHE_NAME_PARAM = "cacheName";
-			String ACTION_PARAM = "action";
-		 	String CLEAR_ALL_REGIONS_ACTION = "clearAllRegions";
-		 	String CLEAR_REGION_ACTION = "clearRegion";
-		 	String REMOVE_ACTION = "remove";
-		 	String DETAIL_ACTION = "detail";
-		 	String REGION_SUMMARY_ACTION = "regionSummary";
-		 	String ITEM_ACTION = "item";
-			String KEY_PARAM = "key";
-			String SILENT_PARAM = "silent";
-
-     		String DEFAULT_TEMPLATE_NAME = "DEFAULT";
-     		String REGION_DETAIL_TEMPLATE_NAME = "DETAIL";
-     		String ITEM_TEMPLATE_NAME = "ITEM";
-     		String REGION_SUMMARY_TEMPLATE_NAME = "SUMMARY";
-
-			String templateName = DEFAULT_TEMPLATE_NAME;
-
-			HashMap<String, Object> context = new HashMap<String, Object>();
-
-			// Get cacheName for actions from request (might be null)
-			String cacheName = request.getParameter( CACHE_NAME_PARAM );
-
-			if ( cacheName != null )
-			{
-			    cacheName = cacheName.trim();
-			}
-
-			// If an action was provided, handle it
-			String action = request.getParameter( ACTION_PARAM );
-
-			if ( action != null )
-			{
-				if ( action.equals( CLEAR_ALL_REGIONS_ACTION ) )
-				{
-					jcsBean.clearAllRegions();
-				}
-				else if ( action.equals( CLEAR_REGION_ACTION ) )
-				{
-					if ( cacheName == null )
-					{
-						// Not Allowed
-					}
-					else
-					{
-						jcsBean.clearRegion( cacheName );
-					}
-				}
-				else if ( action.equals( REMOVE_ACTION ) )
-				{
-					String[] keys = request.getParameterValues( KEY_PARAM );
-
-					for ( int i = 0; i < keys.length; i++ )
-					{
-						jcsBean.removeItem( cacheName, keys[ i ] );
-					}
-
-					templateName = REGION_DETAIL_TEMPLATE_NAME;
-				}
-				else if ( action.equals( DETAIL_ACTION ) )
-				{
-					templateName = REGION_DETAIL_TEMPLATE_NAME;
-				}
-				else if ( action.equals( ITEM_ACTION ) )
-				{
-					templateName = ITEM_TEMPLATE_NAME;
-				}
-				else if ( action.equals( REGION_SUMMARY_ACTION ) )
-				{
-					templateName = REGION_SUMMARY_TEMPLATE_NAME;
-				}
-			}
-
-			if ( request.getParameter( SILENT_PARAM ) != null )
-			{
-				// If silent parameter was passed, no output should be produced.
-				//return null;
-			}
-			else
-			{
-				// Populate the context based on the template
-				if ( templateName == REGION_DETAIL_TEMPLATE_NAME )
-				{
-					//context.put( "cacheName", cacheName );
-					context.put( "elementInfoRecords", jcsBean.buildElementInfo( cacheName ) );
-				}
-				else if ( templateName == DEFAULT_TEMPLATE_NAME )
-				{
-					context.put( "cacheInfoRecords", jcsBean.buildCacheInfo() );
-				}
-			}
-
-///////////////////////////////////////////////////////////////////////////////////
-			//handle display
-
-			if ( templateName == ITEM_TEMPLATE_NAME )
-			{
-			    String key = request.getParameter( KEY_PARAM );
-
-			    if ( key != null )
-			    {
-			        key = key.trim();
-			    }
-
-			    CacheAccess<Serializable, Serializable> cache = JCS.getInstance( cacheName );
-				org.apache.commons.jcs.engine.behavior.ICacheElement<?, ?> element = cache.getCacheElement( key );
-%>
-<h1> Item for key [<%=key%>] in region [<%=cacheName%>] </h1>
-
-<a href="JCSAdmin.jsp?action=detail&cacheName=<%=cacheName%>">Region Detail</a>
-| <a href="JCSAdmin.jsp">All Regions</a>
-
-  <pre>
-	<%=element%>
-  </pre>
-<%
-			}
-			else if ( templateName == REGION_SUMMARY_TEMPLATE_NAME )
-			{
-%>
-
-<h1> Summary for region [<%=cacheName%>] </h1>
-
-<a href="JCSAdmin.jsp">All Regions</a>
-
-<%
-    CacheAccess<?, ?> cache = JCS.getInstance( cacheName );
-    String stats = cache.getStats();
-%>
-
-    <br>
-<b> Stats for region [<%=cacheName%>] </b>
-
-    <pre>
-    	<%=stats%>
-    </pre>
-
-<%
-			}
-			else if ( templateName == REGION_DETAIL_TEMPLATE_NAME )
-			{
-%>
-
-<h1> Detail for region [<%=cacheName%>] </h1>
-
-<a href="JCSAdmin.jsp">All Regions</a>
-
-<table border="1" cellpadding="5" >
-    <tr>
-        <th> Key </th>
-        <th> Eternal? </th>
-        <th> Create time </th>
-        <th> Max Life (s) </th>
-        <th> Till Expiration (s) </th>
-    </tr>
-<%
-	CacheElementInfo[] list = (CacheElementInfo[]) context.get( "elementInfoRecords" );
-    for (CacheElementInfo element : list)
-    {
-%>
-        <tr>
-            <td> <%=element.getKey()%> </td>
-            <td> <%=element.isEternal()%> </td>
-            <td> <%=element.getCreateTime()%> </td>
-            <td> <%=element.getMaxLifeSeconds()%> </td>
-            <td> <%=element.getExpiresInSeconds()%> </td>
-            <td>
-             <a href="JCSAdmin.jsp?action=item&cacheName=<%=cacheName%>&key=<%=element.getKey()%>"> View </a>
-            | <a href="JCSAdmin.jsp?action=remove&cacheName=<%=cacheName%>&key=<%=element.getKey()%>"> Remove </a>
-            </td>
-        </tr>
-<%
-    }
-
-    CacheAccess<?, ?> cache = JCS.getInstance( cacheName );
-    String stats = cache.getStats();
-%>
-    </table>
-
-    <br>
-<b> Stats for region [<%=cacheName%>] </b>
-
-    <pre>
-    	<%=stats%>
-    </pre>
-<%
-  }
-  else
-  {
-%>
-
-<h1> Cache Regions </h1>
-
-<p>
-These are the regions which are currently defined in the cache. 'Items' and
-'Bytes' refer to the elements currently in memory (not spooled). You can clear
-all items for a region by selecting 'Remove all' next to the desired region
-below. You can also <a href="javascript:decision('Clicking OK will clear all the data from all regions!','JCSAdmin.jsp?action=clearAllRegions')">Clear all regions</a>
-which empties the entire cache.
-</p>
-<p>
-	<form action="JCSAdmin.jsp">
-		<input type="hidden" name="action" value="item">
-		Retrieve (key) <input type="text" name="key"> &nbsp;
-		(region) <select name="cacheName">
-<%
-  CacheRegionInfo[] listSelect = (CacheRegionInfo[]) context.get( "cacheInfoRecords" );
-  for (CacheRegionInfo record : listSelect)
-  {
-	%>
-    <option value="<%=record.getCacheName()%>"><%=record.getCacheName()%></option>
-	<%
-  }
-%>
-				</select>
-		<input type="submit">
-	</form>
-</p>
-
-<table border="1" cellpadding="5" >
-    <tr>
-        <th> Cache Name </th>
-        <th> Items </th>
-        <th> Bytes </th>
-        <th> Status </th>
-        <th> Memory Hits </th>
-        <th> Aux Hits </th>
-        <th> Not Found Misses </th>
-        <th> Expired Misses </th>
-    </tr>
-
-<%
-	CacheRegionInfo[] list = (CacheRegionInfo[]) context.get( "cacheInfoRecords" );
-    for (CacheRegionInfo record : listSelect)
-    {
-%>
-        <tr>
-            <td> <%=record.getCacheName()%> </td>
-            <td> <%=record.getCacheSize()%> </td>
-            <td> <%=record.getByteCount()%> </td>
-            <td> <%=record.getCacheStatus()%> </td>
-            <td> <%=record.getHitCountRam()%> </td>
-            <td> <%=record.getHitCountAux()%> </td>
-            <td> <%=record.getMissCountNotFound()%> </td>
-            <td> <%=record.getMissCountExpired()%> </td>
-            <td>
-                <a href="JCSAdmin.jsp?action=regionSummary&cacheName=<%=record.getCacheName()%>"> Summary </a>
-                | <a href="JCSAdmin.jsp?action=detail&cacheName=<%=record.getCacheName()%>"> Detail </a>
-                | <a href="javascript:decision('Clicking OK will remove all the data from the region [<%=record.getCacheName()%>]!','JCSAdmin.jsp?action=clearRegion&cacheName=<%=record.getCacheName()%>')"> Clear </a>
-            </td>
-        </tr>
-<%
-    }
-%>
-    </table>
-<%
-  }
-%>
-
-
-</body>
-
-</html>
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/admin/JCSAdminBean.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/admin/JCSAdminBean.java
deleted file mode 100644
index 42fa3b7..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/admin/JCSAdminBean.java
+++ /dev/null
@@ -1,391 +0,0 @@
-package org.apache.commons.jcs.admin;
-
-import java.io.IOException;
-import java.io.ObjectOutputStream;
-import java.io.Serializable;
-import java.text.DateFormat;
-import java.util.Date;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.TreeSet;
-import java.util.stream.Collectors;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.access.exception.CacheException;
-import org.apache.commons.jcs.auxiliary.remote.server.RemoteCacheServer;
-import org.apache.commons.jcs.auxiliary.remote.server.RemoteCacheServerFactory;
-import org.apache.commons.jcs.engine.CacheElementSerialized;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-import org.apache.commons.jcs.engine.control.CompositeCache;
-import org.apache.commons.jcs.engine.control.CompositeCacheManager;
-import org.apache.commons.jcs.engine.memory.behavior.IMemoryCache;
-
-/**
- * A servlet which provides HTTP access to JCS. Allows a summary of regions to be viewed, and
- * removeAll to be run on individual regions or all regions. Also provides the ability to remove
- * items (any number of key arguments can be provided with action 'remove'). Should be initialized
- * with a properties file that provides at least a classpath resource loader.
- */
-public class JCSAdminBean implements JCSJMXBean
-{
-    /** The cache manager. */
-    private final CompositeCacheManager cacheHub;
-
-    /**
-     * Default constructor
-     */
-    public JCSAdminBean()
-    {
-        super();
-        try
-        {
-            this.cacheHub = CompositeCacheManager.getInstance();
-        }
-        catch (CacheException e)
-        {
-            throw new RuntimeException("Could not retrieve cache manager instance", e);
-        }
-    }
-
-    /**
-     * Parameterized constructor
-     *
-	 * @param cacheHub the cache manager instance
-	 */
-	public JCSAdminBean(CompositeCacheManager cacheHub)
-	{
-		super();
-		this.cacheHub = cacheHub;
-	}
-
-	/**
-     * Builds up info about each element in a region.
-     * <p>
-     * @param cacheName
-     * @return List of CacheElementInfo objects
-     * @throws IOException
-     */
-    @Override
-    public List<CacheElementInfo> buildElementInfo( String cacheName )
-        throws IOException
-    {
-        CompositeCache<Object, Object> cache = cacheHub.getCache( cacheName );
-
-        // Convert all keys to string, store in a sorted map
-        TreeMap<String, ?> keys = new TreeMap<>(cache.getMemoryCache().getKeySet()
-                .stream()
-                .collect(Collectors.toMap(k -> k.toString(), k -> k)));
-
-        LinkedList<CacheElementInfo> records = new LinkedList<>();
-
-        DateFormat format = DateFormat.getDateTimeInstance( DateFormat.SHORT, DateFormat.SHORT );
-
-        long now = System.currentTimeMillis();
-
-        for (Map.Entry<String, ?> key : keys.entrySet())
-        {
-            ICacheElement<?, ?> element = cache.getMemoryCache().getQuiet( key.getValue() );
-
-            IElementAttributes attributes = element.getElementAttributes();
-
-            CacheElementInfo elementInfo = new CacheElementInfo(
-            		key.getKey(),
-            		attributes.getIsEternal(),
-            		format.format(new Date(attributes.getCreateTime())),
-            		attributes.getMaxLife(),
-            		(now - attributes.getCreateTime() - attributes.getMaxLife() * 1000 ) / -1000);
-
-            records.add( elementInfo );
-        }
-
-        return records;
-    }
-
-    /**
-     * Builds up data on every region.
-     * <p>
-     * TODO we need a most light weight method that does not count bytes. The byte counting can
-     *       really swamp a server.
-     * @return List of CacheRegionInfo objects
-     */
-    @Override
-    public List<CacheRegionInfo> buildCacheInfo()
-    {
-        TreeSet<String> cacheNames = new TreeSet<>(cacheHub.getCacheNames());
-
-        LinkedList<CacheRegionInfo> cacheInfo = new LinkedList<>();
-
-        for (String cacheName : cacheNames)
-        {
-            CompositeCache<?, ?> cache = cacheHub.getCache( cacheName );
-
-            CacheRegionInfo regionInfo = new CacheRegionInfo(
-                    cache.getCacheName(),
-                    cache.getSize(),
-                    cache.getStatus().toString(),
-                    cache.getStats(),
-                    cache.getHitCountRam(),
-                    cache.getHitCountAux(),
-                    cache.getMissCountNotFound(),
-                    cache.getMissCountExpired(),
-                    getByteCount( cache ));
-
-            cacheInfo.add( regionInfo );
-        }
-
-        return cacheInfo;
-    }
-
-
-	/**
-     * Tries to estimate how much data is in a region. This is expensive. If there are any non serializable objects in
-     * the region or an error occurs, suppresses exceptions and returns 0.
-     * <p>
-     *
-     * @return int The size of the region in bytes.
-     */
-	@Override
-    public long getByteCount(String cacheName)
-	{
-		return getByteCount(cacheHub.getCache(cacheName));
-	}
-
-	/**
-     * Tries to estimate how much data is in a region. This is expensive. If there are any non serializable objects in
-     * the region or an error occurs, suppresses exceptions and returns 0.
-     * <p>
-     *
-     * @return int The size of the region in bytes.
-     */
-    public <K, V> long getByteCount(CompositeCache<K, V> cache)
-    {
-        if (cache == null)
-        {
-            throw new IllegalArgumentException("The cache object specified was null.");
-        }
-
-        long size = 0;
-        IMemoryCache<K, V> memCache = cache.getMemoryCache();
-
-        for (K key : memCache.getKeySet())
-        {
-            ICacheElement<K, V> ice = null;
-			try
-			{
-				ice = memCache.get(key);
-			}
-			catch (IOException e)
-			{
-                throw new RuntimeException("IOException while trying to get a cached element", e);
-			}
-
-			if (ice == null)
-			{
-				continue;
-			}
-
-			if (ice instanceof CacheElementSerialized)
-            {
-                size += ((CacheElementSerialized<K, V>) ice).getSerializedValue().length;
-            }
-            else
-            {
-                Object element = ice.getVal();
-
-                //CountingOnlyOutputStream: Keeps track of the number of bytes written to it, but doesn't write them anywhere.
-                CountingOnlyOutputStream counter = new CountingOnlyOutputStream();
-                try (ObjectOutputStream out = new ObjectOutputStream(counter);)
-                {
-                    out.writeObject(element);
-                }
-                catch (IOException e)
-                {
-                    throw new RuntimeException("IOException while trying to measure the size of the cached element", e);
-                }
-                finally
-                {
-                	try
-                	{
-						counter.close();
-					}
-                	catch (IOException e)
-                	{
-                		// ignore
-					}
-                }
-
-                // 4 bytes lost for the serialization header
-                size += counter.getCount() - 4;
-            }
-        }
-
-        return size;
-    }
-
-    /**
-     * Clears all regions in the cache.
-     * <p>
-     * If this class is running within a remote cache server, clears all regions via the <code>RemoteCacheServer</code>
-     * API, so that removes will be broadcast to client machines. Otherwise clears all regions in the cache directly via
-     * the usual cache API.
-     */
-    @Override
-    public void clearAllRegions() throws IOException
-    {
-        RemoteCacheServer<?, ?> remoteCacheServer = RemoteCacheServerFactory.getRemoteCacheServer();
-
-        if (remoteCacheServer == null)
-        {
-            // Not running in a remote cache server.
-            // Remove objects from the cache directly, as no need to broadcast removes to client machines...
-            for (String name : cacheHub.getCacheNames())
-            {
-                cacheHub.getCache(name).removeAll();
-            }
-        }
-        else
-        {
-            // Running in a remote cache server.
-            // Remove objects via the RemoteCacheServer API, so that removes will be broadcast to client machines...
-            // Call remoteCacheServer.removeAll(String) for each cacheName...
-            for (String name : cacheHub.getCacheNames())
-            {
-                remoteCacheServer.removeAll(name);
-            }
-        }
-    }
-
-    /**
-     * Clears a particular cache region.
-     * <p>
-     * If this class is running within a remote cache server, clears the region via the <code>RemoteCacheServer</code>
-     * API, so that removes will be broadcast to client machines. Otherwise clears the region directly via the usual
-     * cache API.
-     */
-    @Override
-    public void clearRegion(String cacheName) throws IOException
-    {
-        if (cacheName == null)
-        {
-            throw new IllegalArgumentException("The cache name specified was null.");
-        }
-        if (RemoteCacheServerFactory.getRemoteCacheServer() == null)
-        {
-            // Not running in a remote cache server.
-            // Remove objects from the cache directly, as no need to broadcast removes to client machines...
-            cacheHub.getCache(cacheName).removeAll();
-        }
-        else
-        {
-            // Running in a remote cache server.
-            // Remove objects via the RemoteCacheServer API, so that removes will be broadcast to client machines...
-            try
-            {
-                // Call remoteCacheServer.removeAll(String)...
-                RemoteCacheServer<?, ?> remoteCacheServer = RemoteCacheServerFactory.getRemoteCacheServer();
-                remoteCacheServer.removeAll(cacheName);
-            }
-            catch (IOException e)
-            {
-                throw new IllegalStateException("Failed to remove all elements from cache region [" + cacheName + "]: " + e, e);
-            }
-        }
-    }
-
-    /**
-     * Removes a particular item from a particular region.
-     * <p>
-     * If this class is running within a remote cache server, removes the item via the <code>RemoteCacheServer</code>
-     * API, so that removes will be broadcast to client machines. Otherwise clears the region directly via the usual
-     * cache API.
-     *
-     * @param cacheName
-     * @param key
-     *
-     * @throws IOException
-     */
-    @Override
-    public void removeItem(String cacheName, String key) throws IOException
-    {
-        if (cacheName == null)
-        {
-            throw new IllegalArgumentException("The cache name specified was null.");
-        }
-        if (key == null)
-        {
-            throw new IllegalArgumentException("The key specified was null.");
-        }
-        if (RemoteCacheServerFactory.getRemoteCacheServer() == null)
-        {
-            // Not running in a remote cache server.
-            // Remove objects from the cache directly, as no need to broadcast removes to client machines...
-            cacheHub.getCache(cacheName).remove(key);
-        }
-        else
-        {
-            // Running in a remote cache server.
-            // Remove objects via the RemoteCacheServer API, so that removes will be broadcast to client machines...
-            try
-            {
-                Object keyToRemove = null;
-                CompositeCache<?, ?> cache = CompositeCacheManager.getInstance().getCache(cacheName);
-
-                // A String key was supplied, but to remove elements via the RemoteCacheServer API, we need the
-                // actual key object as stored in the cache (i.e. a Serializable object). To find the key in this form,
-                // we iterate through all keys stored in the memory cache until we find one whose toString matches
-                // the string supplied...
-                Set<?> allKeysInCache = cache.getMemoryCache().getKeySet();
-                for (Object keyInCache : allKeysInCache)
-                {
-                    if (keyInCache.toString().equals(key))
-                    {
-                        if (keyToRemove == null)
-                        {
-                            keyToRemove = keyInCache;
-                        }
-                        else
-                        {
-                            // A key matching the one specified was already found...
-                            throw new IllegalStateException("Unexpectedly found duplicate keys in the cache region matching the key specified.");
-                        }
-                    }
-                }
-                if (keyToRemove == null)
-                {
-                    throw new IllegalStateException("No match for this key could be found in the set of keys retrieved from the memory cache.");
-                }
-                // At this point, we have retrieved the matching K key.
-
-                // Call remoteCacheServer.remove(String, Serializable)...
-                RemoteCacheServer<Serializable, Serializable> remoteCacheServer = RemoteCacheServerFactory.getRemoteCacheServer();
-                remoteCacheServer.remove(cacheName, key);
-            }
-            catch (Exception e)
-            {
-                throw new IllegalStateException("Failed to remove element with key [" + key + ", " + key.getClass() + "] from cache region [" + cacheName + "]: " + e, e);
-            }
-        }
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/admin/JCSJMXBean.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/admin/JCSJMXBean.java
deleted file mode 100644
index 41b756a..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/admin/JCSJMXBean.java
+++ /dev/null
@@ -1,91 +0,0 @@
-package org.apache.commons.jcs.admin;
-
-import java.io.IOException;
-import java.util.List;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import javax.management.MXBean;
-
-/**
- * A MXBean to expose the JCS statistics to JMX
- */
-@MXBean
-public interface JCSJMXBean
-{
-    /**
-     * Builds up info about each element in a region.
-     * <p>
-     * @param cacheName
-     * @return List of CacheElementInfo objects
-     * @throws IOException
-     */
-    List<CacheElementInfo> buildElementInfo( String cacheName ) throws IOException;
-
-    /**
-     * Builds up data on every region.
-     * <p>
-     * TODO we need a most light weight method that does not count bytes. The byte counting can
-     *       really swamp a server.
-     * @return List of CacheRegionInfo objects
-     */
-    List<CacheRegionInfo> buildCacheInfo();
-
-    /**
-     * Tries to estimate how much data is in a region. This is expensive. If there are any non serializable objects in
-     * the region or an error occurs, suppresses exceptions and returns 0.
-     * <p>
-     *
-     * @return long The size of the region in bytes.
-     */
-    long getByteCount(String cacheName);
-
-    /**
-     * Clears all regions in the cache.
-     * <p>
-     * If this class is running within a remote cache server, clears all regions via the <code>RemoteCacheServer</code>
-     * API, so that removes will be broadcast to client machines. Otherwise clears all regions in the cache directly via
-     * the usual cache API.
-     */
-    void clearAllRegions() throws IOException;
-
-    /**
-     * Clears a particular cache region.
-     * <p>
-     * If this class is running within a remote cache server, clears the region via the <code>RemoteCacheServer</code>
-     * API, so that removes will be broadcast to client machines. Otherwise clears the region directly via the usual
-     * cache API.
-     */
-    void clearRegion(String cacheName) throws IOException;
-
-    /**
-     * Removes a particular item from a particular region.
-     * <p>
-     * If this class is running within a remote cache server, removes the item via the <code>RemoteCacheServer</code>
-     * API, so that removes will be broadcast to client machines. Otherwise clears the region directly via the usual
-     * cache API.
-     *
-     * @param cacheName
-     * @param key
-     *
-     * @throws IOException
-     */
-    void removeItem(String cacheName, String key) throws IOException;
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/AbstractAuxiliaryCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/AbstractAuxiliaryCache.java
deleted file mode 100644
index b0ee573..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/AbstractAuxiliaryCache.java
+++ /dev/null
@@ -1,257 +0,0 @@
-package org.apache.commons.jcs.auxiliary;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.engine.logging.CacheEvent;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEvent;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-import org.apache.commons.jcs.engine.match.KeyMatcherPatternImpl;
-import org.apache.commons.jcs.engine.match.behavior.IKeyMatcher;
-import org.apache.commons.jcs.utils.serialization.StandardSerializer;
-
-/** This holds convenience methods used by most auxiliary caches. */
-public abstract class AbstractAuxiliaryCache<K, V>
-    implements AuxiliaryCache<K, V>
-{
-    /** An optional event logger */
-    private ICacheEventLogger cacheEventLogger;
-
-    /** The serializer. Uses a standard serializer by default. */
-    private IElementSerializer elementSerializer = new StandardSerializer();
-
-    /** Key matcher used by the getMatching API */
-    private IKeyMatcher<K> keyMatcher = new KeyMatcherPatternImpl<>();
-
-    /**
-     * Gets multiple items from the cache based on the given set of keys.
-     *
-     * @param keys
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache for any of these keys
-     */
-    protected Map<K, ICacheElement<K, V>> processGetMultiple(Set<K> keys) throws IOException
-    {
-        if (keys != null)
-        {
-            return keys.stream()
-                .map(key -> {
-                    try
-                    {
-                        return get(key);
-                    }
-                    catch (IOException e)
-                    {
-                        return null;
-                    }
-                })
-                .filter(element -> element != null)
-                .collect(Collectors.toMap(
-                        element -> element.getKey(),
-                        element -> element));
-        }
-
-        return new HashMap<>();
-    }
-
-    /**
-     * Gets the item from the cache.
-     *
-     * @param key
-     * @return ICacheElement, a wrapper around the key, value, and attributes
-     * @throws IOException
-     */
-    @Override
-    public abstract ICacheElement<K, V> get( K key ) throws IOException;
-
-    /**
-     * Logs an event if an event logger is configured.
-     * <p>
-     * @param item
-     * @param eventName
-     * @return ICacheEvent
-     */
-    protected ICacheEvent<K> createICacheEvent( ICacheElement<K, V> item, String eventName )
-    {
-        if ( cacheEventLogger == null )
-        {
-            return new CacheEvent<>();
-        }
-        String diskLocation = getEventLoggingExtraInfo();
-        String regionName = null;
-        K key = null;
-        if ( item != null )
-        {
-            regionName = item.getCacheName();
-            key = item.getKey();
-        }
-        return cacheEventLogger.createICacheEvent( getAuxiliaryCacheAttributes().getName(), regionName, eventName,
-                                                   diskLocation, key );
-    }
-
-    /**
-     * Logs an event if an event logger is configured.
-     * <p>
-     * @param regionName
-     * @param key
-     * @param eventName
-     * @return ICacheEvent
-     */
-    protected <T> ICacheEvent<T> createICacheEvent( String regionName, T key, String eventName )
-    {
-        if ( cacheEventLogger == null )
-        {
-            return new CacheEvent<>();
-        }
-        String diskLocation = getEventLoggingExtraInfo();
-        return cacheEventLogger.createICacheEvent( getAuxiliaryCacheAttributes().getName(), regionName, eventName,
-                                                   diskLocation, key );
-
-    }
-
-    /**
-     * Logs an event if an event logger is configured.
-     * <p>
-     * @param cacheEvent
-     */
-    protected <T> void logICacheEvent( ICacheEvent<T> cacheEvent )
-    {
-        if ( cacheEventLogger != null )
-        {
-            cacheEventLogger.logICacheEvent( cacheEvent );
-        }
-    }
-
-    /**
-     * Logs an event if an event logger is configured.
-     * <p>
-     * @param source
-     * @param eventName
-     * @param optionalDetails
-     */
-    protected void logApplicationEvent( String source, String eventName, String optionalDetails )
-    {
-        if ( cacheEventLogger != null )
-        {
-            cacheEventLogger.logApplicationEvent( source, eventName, optionalDetails );
-        }
-    }
-
-    /**
-     * Logs an event if an event logger is configured.
-     * <p>
-     * @param source
-     * @param eventName
-     * @param errorMessage
-     */
-    protected void logError( String source, String eventName, String errorMessage )
-    {
-        if ( cacheEventLogger != null )
-        {
-            cacheEventLogger.logError( source, eventName, errorMessage );
-        }
-    }
-
-    /**
-     * Gets the extra info for the event log.
-     * <p>
-     * @return IP, or disk location, etc.
-     */
-    public abstract String getEventLoggingExtraInfo();
-
-    /**
-     * Allows it to be injected.
-     * <p>
-     * @param cacheEventLogger
-     */
-    @Override
-    public void setCacheEventLogger( ICacheEventLogger cacheEventLogger )
-    {
-        this.cacheEventLogger = cacheEventLogger;
-    }
-
-    /**
-     * Allows it to be injected.
-     * <p>
-     * @return cacheEventLogger
-     */
-    public ICacheEventLogger getCacheEventLogger()
-    {
-        return this.cacheEventLogger;
-    }
-
-    /**
-     * Allows you to inject a custom serializer. A good example would be a compressing standard
-     * serializer.
-     * <p>
-     * Does not allow you to set it to null.
-     * <p>
-     * @param elementSerializer
-     */
-    @Override
-    public void setElementSerializer( IElementSerializer elementSerializer )
-    {
-        if ( elementSerializer != null )
-        {
-            this.elementSerializer = elementSerializer;
-        }
-    }
-
-    /**
-     * Allows it to be injected.
-     * <p>
-     * @return elementSerializer
-     */
-    public IElementSerializer getElementSerializer()
-    {
-        return this.elementSerializer;
-    }
-
-    /**
-     * Sets the key matcher used by get matching.
-     * <p>
-     * @param keyMatcher
-     */
-    @Override
-    public void setKeyMatcher( IKeyMatcher<K> keyMatcher )
-    {
-        if ( keyMatcher != null )
-        {
-            this.keyMatcher = keyMatcher;
-        }
-    }
-
-    /**
-     * Returns the key matcher used by get matching.
-     * <p>
-     * @return keyMatcher
-     */
-    public IKeyMatcher<K> getKeyMatcher()
-    {
-        return this.keyMatcher;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/AbstractAuxiliaryCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/AbstractAuxiliaryCacheAttributes.java
deleted file mode 100644
index e268c60..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/AbstractAuxiliaryCacheAttributes.java
+++ /dev/null
@@ -1,146 +0,0 @@
-package org.apache.commons.jcs.auxiliary;
-
-import org.apache.commons.jcs.engine.behavior.ICacheEventQueue;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * This has common attributes used by all auxiliaries.
- */
-public abstract class AbstractAuxiliaryCacheAttributes
-    implements AuxiliaryCacheAttributes
-{
-    /** Don't change */
-    private static final long serialVersionUID = -6594609334959187673L;
-
-    /** cacheName */
-    private String cacheName;
-
-    /** name */
-    private String name;
-
-    /** eventQueueType -- pooled, or single threaded */
-    private ICacheEventQueue.QueueType eventQueueType;
-
-    /** Named when pooled */
-    private String eventQueuePoolName;
-
-    /**
-     * @param name
-     */
-    @Override
-    public void setCacheName( String name )
-    {
-        this.cacheName = name;
-    }
-
-    /**
-     * Gets the cacheName attribute of the AuxiliaryCacheAttributes object
-     * <p>
-     * @return The cacheName value
-     */
-    @Override
-    public String getCacheName()
-    {
-        return this.cacheName;
-    }
-
-    /**
-     * This is the name of the auxiliary in configuration file.
-     * <p>
-     * @see org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes#setName(java.lang.String)
-     */
-    @Override
-    public void setName( String s )
-    {
-        this.name = s;
-    }
-
-    /**
-     * Gets the name attribute of the AuxiliaryCacheAttributes object
-     * <p>
-     * @return The name value
-     */
-    @Override
-    public String getName()
-    {
-        return this.name;
-    }
-
-    /**
-     * SINGLE is the default. If you choose POOLED, the value of EventQueuePoolName will be used
-     * <p>
-     * @param queueType SINGLE or POOLED
-     */
-    @Override
-    public void setEventQueueType( ICacheEventQueue.QueueType queueType )
-    {
-        this.eventQueueType = queueType;
-    }
-
-    /**
-     * @return SINGLE or POOLED
-     */
-    @Override
-    public ICacheEventQueue.QueueType getEventQueueType()
-    {
-        return eventQueueType;
-    }
-
-    /**
-     * If you choose a POOLED event queue type, the value of EventQueuePoolName will be used. This
-     * is ignored if the pool type is SINGLE
-     * <p>
-     * @param s SINGLE or POOLED
-     */
-    @Override
-    public void setEventQueuePoolName( String s )
-    {
-        eventQueuePoolName = s;
-    }
-
-    /**
-     * Sets the pool name to use. If a pool is not found by this name, the thread pool manager will
-     * return a default configuration.
-     * <p>
-     * @return name of thread pool to use for this auxiliary
-     */
-    @Override
-    public String getEventQueuePoolName()
-    {
-        return eventQueuePoolName;
-    }
-
-    /**
-     * @see java.lang.Object#clone()
-     */
-    @Override
-    public AbstractAuxiliaryCacheAttributes clone()
-    {
-        try
-        {
-            return (AbstractAuxiliaryCacheAttributes)super.clone();
-        }
-        catch (CloneNotSupportedException e)
-        {
-            throw new RuntimeException("Clone not supported. This should never happen.", e);
-        }
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/AbstractAuxiliaryCacheEventLogging.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/AbstractAuxiliaryCacheEventLogging.java
deleted file mode 100644
index 9b74c60..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/AbstractAuxiliaryCacheEventLogging.java
+++ /dev/null
@@ -1,342 +0,0 @@
-package org.apache.commons.jcs.auxiliary;
-
-import java.io.IOException;
-import java.io.Serializable;
-import java.util.Map;
-import java.util.Set;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEvent;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-
-/**
- * All ICacheEvents are defined as final. Children must implement process events. These are wrapped
- * in event log parent calls.
- *
- * You can override the public method, but if you don't, the default will call getWithTiming.
- */
-public abstract class AbstractAuxiliaryCacheEventLogging<K, V>
-    extends AbstractAuxiliaryCache<K, V>
-{
-    /**
-     * Puts an item into the cache.
-     *
-     * @param cacheElement
-     * @throws IOException
-     */
-    @Override
-    public void update( ICacheElement<K, V> cacheElement )
-        throws IOException
-    {
-        updateWithEventLogging( cacheElement );
-    }
-
-    /**
-     * Puts an item into the cache. Wrapped in logging.
-     *
-     * @param cacheElement
-     * @throws IOException
-     */
-    protected final void updateWithEventLogging( ICacheElement<K, V> cacheElement )
-        throws IOException
-    {
-        ICacheEvent<K> cacheEvent = createICacheEvent( cacheElement, ICacheEventLogger.UPDATE_EVENT );
-        try
-        {
-            processUpdate( cacheElement );
-        }
-        finally
-        {
-            logICacheEvent( cacheEvent );
-        }
-    }
-
-    /**
-     * Implementation of put.
-     *
-     * @param cacheElement
-     * @throws IOException
-     */
-    protected abstract void processUpdate( ICacheElement<K, V> cacheElement )
-        throws IOException;
-
-    /**
-     * Gets the item from the cache.
-     *
-     * @param key
-     * @return ICacheElement, a wrapper around the key, value, and attributes
-     * @throws IOException
-     */
-    @Override
-    public ICacheElement<K, V> get( K key )
-        throws IOException
-    {
-        return getWithEventLogging( key );
-    }
-
-    /**
-     * Gets the item from the cache. Wrapped in logging.
-     *
-     * @param key
-     * @return ICacheElement, a wrapper around the key, value, and attributes
-     * @throws IOException
-     */
-    protected final ICacheElement<K, V> getWithEventLogging( K key )
-        throws IOException
-    {
-        ICacheEvent<K> cacheEvent = createICacheEvent( getCacheName(), key, ICacheEventLogger.GET_EVENT );
-        try
-        {
-            return processGet( key );
-        }
-        finally
-        {
-            logICacheEvent( cacheEvent );
-        }
-    }
-
-    /**
-     * Implementation of get.
-     *
-     * @param key
-     * @return ICacheElement, a wrapper around the key, value, and attributes
-     * @throws IOException
-     */
-    protected abstract ICacheElement<K, V> processGet( K key )
-        throws IOException;
-
-    /**
-     * Gets multiple items from the cache based on the given set of keys.
-     *
-     * @param keys
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache for any of these keys
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMultiple(Set<K> keys)
-        throws IOException
-    {
-        return getMultipleWithEventLogging( keys );
-    }
-
-    /**
-     * Gets multiple items from the cache based on the given set of keys.
-     *
-     * @param keys
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache for any of these keys
-     * @throws IOException
-     */
-    protected final Map<K, ICacheElement<K, V>> getMultipleWithEventLogging(Set<K> keys )
-        throws IOException
-    {
-        ICacheEvent<Serializable> cacheEvent = createICacheEvent( getCacheName(), (Serializable) keys,
-                                                    ICacheEventLogger.GETMULTIPLE_EVENT );
-        try
-        {
-            return processGetMultiple( keys );
-        }
-        finally
-        {
-            logICacheEvent( cacheEvent );
-        }
-    }
-
-    /**
-     * Gets items from the cache matching the given pattern. Items from memory will replace those
-     * from remote sources.
-     *
-     * This only works with string keys. It's too expensive to do a toString on every key.
-     *
-     * Auxiliaries will do their best to handle simple expressions. For instance, the JDBC disk
-     * cache will convert * to % and . to _
-     *
-     * @param pattern
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data matching the pattern.
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMatching( String pattern )
-        throws IOException
-    {
-        return getMatchingWithEventLogging( pattern );
-    }
-
-    /**
-     * Gets matching items from the cache based on the given pattern.
-     *
-     * @param pattern
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data matching the pattern.
-     * @throws IOException
-     */
-    protected final Map<K, ICacheElement<K, V>> getMatchingWithEventLogging( String pattern )
-        throws IOException
-    {
-        ICacheEvent<String> cacheEvent = createICacheEvent( getCacheName(), pattern, ICacheEventLogger.GETMATCHING_EVENT );
-        try
-        {
-            return processGetMatching( pattern );
-        }
-        finally
-        {
-            logICacheEvent( cacheEvent );
-        }
-    }
-
-    /**
-     * Implementation of getMatching.
-     *
-     * @param pattern
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data matching the pattern.
-     * @throws IOException
-     */
-    protected abstract Map<K, ICacheElement<K, V>> processGetMatching( String pattern )
-        throws IOException;
-
-    /**
-     * Removes the item from the cache. Wraps the remove in event logs.
-     *
-     * @param key
-     * @return boolean, whether or not the item was removed
-     * @throws IOException
-     */
-    @Override
-    public boolean remove( K key )
-        throws IOException
-    {
-        return removeWithEventLogging( key );
-    }
-
-    /**
-     * Removes the item from the cache. Wraps the remove in event logs.
-     *
-     * @param key
-     * @return boolean, whether or not the item was removed
-     * @throws IOException
-     */
-    protected final boolean removeWithEventLogging( K key )
-        throws IOException
-    {
-        ICacheEvent<K> cacheEvent = createICacheEvent( getCacheName(), key, ICacheEventLogger.REMOVE_EVENT );
-        try
-        {
-            return processRemove( key );
-        }
-        finally
-        {
-            logICacheEvent( cacheEvent );
-        }
-    }
-
-    /**
-     * Specific implementation of remove.
-     *
-     * @param key
-     * @return boolean, whether or not the item was removed
-     * @throws IOException
-     */
-    protected abstract boolean processRemove( K key )
-        throws IOException;
-
-    /**
-     * Removes all from the region. Wraps the removeAll in event logs.
-     *
-     * @throws IOException
-     */
-    @Override
-    public void removeAll()
-        throws IOException
-    {
-        removeAllWithEventLogging();
-    }
-
-    /**
-     * Removes all from the region. Wraps the removeAll in event logs.
-     *
-     * @throws IOException
-     */
-    protected final void removeAllWithEventLogging()
-        throws IOException
-    {
-        ICacheEvent<String> cacheEvent = createICacheEvent( getCacheName(), "all", ICacheEventLogger.REMOVEALL_EVENT );
-        try
-        {
-            processRemoveAll();
-        }
-        finally
-        {
-            logICacheEvent( cacheEvent );
-        }
-    }
-
-    /**
-     * Specific implementation of removeAll.
-     *
-     * @throws IOException
-     */
-    protected abstract void processRemoveAll()
-        throws IOException;
-
-    /**
-     * Synchronously dispose the remote cache; if failed, replace the remote handle with a zombie.
-     *
-     * @throws IOException
-     */
-    @Override
-    public void dispose()
-        throws IOException
-    {
-        disposeWithEventLogging();
-    }
-
-    /**
-     * Synchronously dispose the remote cache; if failed, replace the remote handle with a zombie.
-     * Wraps the removeAll in event logs.
-     *
-     * @throws IOException
-     */
-    protected final void disposeWithEventLogging()
-        throws IOException
-    {
-        ICacheEvent<String> cacheEvent = createICacheEvent( getCacheName(), "none", ICacheEventLogger.DISPOSE_EVENT );
-        try
-        {
-            processDispose();
-        }
-        finally
-        {
-            logICacheEvent( cacheEvent );
-        }
-    }
-
-    /**
-     * Specific implementation of dispose.
-     *
-     * @throws IOException
-     */
-    protected abstract void processDispose()
-        throws IOException;
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/AbstractAuxiliaryCacheFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/AbstractAuxiliaryCacheFactory.java
deleted file mode 100644
index 657015e..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/AbstractAuxiliaryCacheFactory.java
+++ /dev/null
@@ -1,72 +0,0 @@
-package org.apache.commons.jcs.auxiliary;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheFactory;
-
-/**
- * Base class for auxiliary cache factories.
- */
-public abstract class AbstractAuxiliaryCacheFactory
-    implements AuxiliaryCacheFactory
-{
-    /** The auxiliary name. The composite cache manager keeps this in a map, keyed by name. */
-    private String name = this.getClass().getSimpleName();
-
-    /**
-     * Initialize this factory
-     */
-    @Override
-    public void initialize()
-    {
-        // empty
-    }
-
-    /**
-     * Dispose of this factory, clean up shared resources
-     */
-    @Override
-    public void dispose()
-    {
-        // empty
-    }
-
-    /**
-     * Gets the name attribute of the DiskCacheFactory object
-     * <p>
-     * @return The name value
-     */
-    @Override
-    public String getName()
-    {
-        return this.name;
-    }
-
-    /**
-     * Sets the name attribute of the DiskCacheFactory object
-     * <p>
-     * @param name The new name value
-     */
-    @Override
-    public void setName( String name )
-    {
-        this.name = name;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/AbstractAuxiliaryCacheMonitor.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/AbstractAuxiliaryCacheMonitor.java
deleted file mode 100644
index 25aa937..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/AbstractAuxiliaryCacheMonitor.java
+++ /dev/null
@@ -1,197 +0,0 @@
-package org.apache.commons.jcs.auxiliary;

-

-/*

- * Licensed to the Apache Software Foundation (ASF) under one

- * or more contributor license agreements.  See the NOTICE file

- * distributed with this work for additional information

- * regarding copyright ownership.  The ASF licenses this file

- * to you under the Apache License, Version 2.0 (the

- * "License"); you may not use this file except in compliance

- * with the License.  You may obtain a copy of the License at

- *

- *   http://www.apache.org/licenses/LICENSE-2.0

- *

- * Unless required by applicable law or agreed to in writing,

- * software distributed under the License is distributed on an

- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

- * KIND, either express or implied.  See the License for the

- * specific language governing permissions and limitations

- * under the License.

- */

-

-import java.util.concurrent.atomic.AtomicBoolean;

-import java.util.concurrent.locks.Condition;

-import java.util.concurrent.locks.Lock;

-import java.util.concurrent.locks.ReentrantLock;

-

-import org.apache.commons.jcs.log.Log;

-import org.apache.commons.jcs.log.LogManager;

-

-/**

- * Used to monitor and repair any failed connection for the lateral cache service. By default the

- * monitor operates in a failure driven mode. That is, it goes into a wait state until there is an

- * error. Upon the notification of a connection error, the monitor changes to operate in a time

- * driven mode. That is, it attempts to recover the connections on a periodic basis. When all failed

- * connections are restored, it changes back to the failure driven mode.

- */

-public abstract class AbstractAuxiliaryCacheMonitor extends Thread

-{

-    /** The logger */

-    protected final Log log = LogManager.getLog( this.getClass() );

-

-    /** How long to wait between runs */

-    protected static long idlePeriod = 20 * 1000;

-

-    /**

-     * Must make sure AbstractAuxiliaryCacheMonitor is started before any error can be detected!

-     */

-    protected AtomicBoolean allright = new AtomicBoolean(true);

-

-    /**

-     * shutdown flag

-     */

-    private AtomicBoolean shutdown = new AtomicBoolean(false);

-

-    /** Synchronization helper lock */

-    private Lock lock = new ReentrantLock();

-

-    /** Synchronization helper condition */

-    private Condition trigger = lock.newCondition();

-

-    /**

-     * Constructor

-     *

-     * @param name the thread name

-     */

-    public AbstractAuxiliaryCacheMonitor(String name)

-    {

-        super(name);

-    }

-

-    /**

-     * Configures the idle period between repairs.

-     * <p>

-     * @param idlePeriod The new idlePeriod value

-     */

-    public static void setIdlePeriod( long idlePeriod )

-    {

-        if ( idlePeriod > AbstractAuxiliaryCacheMonitor.idlePeriod )

-        {

-            AbstractAuxiliaryCacheMonitor.idlePeriod = idlePeriod;

-        }

-    }

-

-    /**

-     * Notifies the cache monitor that an error occurred, and kicks off the error recovery process.

-     */

-    public void notifyError()

-    {

-        if (allright.compareAndSet(true, false))

-        {

-            signalTrigger();

-        }

-    }

-

-    /**

-     * Notifies the cache monitor that the service shall shut down

-     */

-    public void notifyShutdown()

-    {

-        if (shutdown.compareAndSet(false, true))

-        {

-            signalTrigger();

-        }

-    }

-

-    // Trigger continuation of loop

-    private void signalTrigger()

-    {

-        try

-        {

-            lock.lock();

-            trigger.signal();

-        }

-        finally

-        {

-            lock.unlock();

-        }

-    }

-

-    /**

-     * Clean up all resources before shutdown

-     */

-    protected abstract void dispose();

-

-    /**

-     * do actual work

-     */

-    protected abstract void doWork();

-

-    /**

-     * Main processing method for the AbstractAuxiliaryCacheMonitor object

-     */

-    @Override

-    public void run()

-    {

-        do

-        {

-            if ( allright.get() )

-            {

-                log.debug( "ERROR DRIVEN MODE: allright = true, cache monitor will wait for an error." );

-            }

-            else

-            {

-                log.debug( "ERROR DRIVEN MODE: allright = false cache monitor running." );

-            }

-

-            if ( allright.get() )

-            {

-                // Failure driven mode.

-                try

-                {

-                    lock.lock();

-                    trigger.await();

-                    // wake up only if there is an error.

-                }

-                catch ( InterruptedException ignore )

-                {

-                    //no op, this is expected

-                }

-                finally

-                {

-                    lock.unlock();

-                }

-            }

-

-            // check for requested shutdown

-            if ( shutdown.get() )

-            {

-                log.info( "Shutting down cache monitor" );

-                dispose();

-                return;

-            }

-

-            // The "allright" flag must be false here.

-            // Simply presume we can fix all the errors until proven otherwise.

-            allright.set(true);

-

-            log.debug( "Cache monitor running." );

-

-            doWork();

-

-            try

-            {

-                // don't want to sleep after waking from an error

-                // run immediately and sleep here.

-                log.debug( "Cache monitor sleeping for {0} between runs.", idlePeriod );

-

-                Thread.sleep( idlePeriod );

-            }

-            catch ( InterruptedException ex )

-            {

-                // ignore;

-            }

-        }

-        while ( true );

-    }

-}

diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/AuxiliaryCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/AuxiliaryCache.java
deleted file mode 100644
index 3bc3a52..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/AuxiliaryCache.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package org.apache.commons.jcs.auxiliary;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.behavior.ICache;
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-import org.apache.commons.jcs.engine.stats.behavior.IStats;
-
-import java.io.IOException;
-import java.util.Set;
-
-/**
- * Tag interface for auxiliary caches. Currently this provides no additional methods over what is in
- * ICache, but I anticipate that will change. For example, there will be a mechanism for determining
- * the type (disk/lateral/remote) of the auxiliary here -- and the existing getCacheType will be
- * removed from ICache.
- */
-public interface AuxiliaryCache<K, V>
-    extends ICache<K, V>
-{
-    /**
-     * Get a set of the keys for all elements in the auxiliary cache.
-     * <p>
-     * @return a set of the key type
-     * TODO This should probably be done in chunks with a range passed in. This
-     *       will be a problem if someone puts a 1,000,000 or so items in a
-     *       region.
-     * @throws IOException if access to the auxiliary cache fails
-     */
-    Set<K> getKeySet() throws IOException;
-
-    /**
-     * @return the historical and statistical data for a region's auxiliary cache.
-     */
-    IStats getStatistics();
-
-    /**
-     * This returns the generic attributes for an auxiliary cache. Most implementations will cast
-     * this to a more specific type.
-     * <p>
-     * @return the attributes for the auxiliary cache
-     */
-    AuxiliaryCacheAttributes getAuxiliaryCacheAttributes();
-
-    /**
-     * Allows you to inject a custom serializer. A good example would be a compressing standard
-     * serializer.
-     * <p>
-     * @param elementSerializer
-     */
-    void setElementSerializer( IElementSerializer elementSerializer );
-
-    /**
-     * Every Auxiliary must allow for the use of an event logger.
-     * <p>
-     * @param cacheEventLogger
-     */
-    void setCacheEventLogger( ICacheEventLogger cacheEventLogger );
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/AuxiliaryCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/AuxiliaryCacheAttributes.java
deleted file mode 100644
index f962cd2..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/AuxiliaryCacheAttributes.java
+++ /dev/null
@@ -1,93 +0,0 @@
-package org.apache.commons.jcs.auxiliary;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.Serializable;
-
-import org.apache.commons.jcs.engine.behavior.ICacheEventQueue;
-
-/**
- * This is a nominal interface that auxiliary cache attributes should implement. This allows the
- * auxiliary mangers to share a common interface.
- */
-public interface AuxiliaryCacheAttributes
-    extends Serializable, Cloneable
-{
-    /**
-     * Sets the name of the cache, referenced by the appropriate manager.
-     * <p>
-     * @param s The new cacheName value
-     */
-    void setCacheName( String s );
-
-    /**
-     * Gets the cacheName attribute of the AuxiliaryCacheAttributes object
-     * <p>
-     * @return The cacheName value
-     */
-    String getCacheName();
-
-    /**
-     * Name known by by configurator
-     * <p>
-     * @param s The new name value
-     */
-    void setName( String s );
-
-    /**
-     * Gets the name attribute of the AuxiliaryCacheAttributes object
-     * <p>
-     * @return The name value
-     */
-    String getName();
-
-    /**
-     * SINGLE is the default. If you choose POOLED, the value of EventQueuePoolName will be used
-     * <p>
-     * @param s SINGLE or POOLED
-     */
-    void setEventQueueType( ICacheEventQueue.QueueType s );
-
-    /**
-     * @return SINGLE or POOLED
-     */
-    ICacheEventQueue.QueueType getEventQueueType();
-
-    /**
-     * If you choose a POOLED event queue type, the value of EventQueuePoolName will be used. This
-     * is ignored if the pool type is SINGLE
-     * <p>
-     * @param s SINGLE or POOLED
-     */
-    void setEventQueuePoolName( String s );
-
-    /**
-     * Sets the pool name to use. If a pool is not found by this name, the thread pool manager will
-     * return a default configuration.
-     * <p>
-     * @return name of thread pool to use for this auxiliary
-     */
-    String getEventQueuePoolName();
-
-    /**
-     * Clone object
-     */
-    AuxiliaryCacheAttributes clone();
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/AuxiliaryCacheConfigurator.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/AuxiliaryCacheConfigurator.java
deleted file mode 100644
index 02aea4e..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/AuxiliaryCacheConfigurator.java
+++ /dev/null
@@ -1,117 +0,0 @@
-package org.apache.commons.jcs.auxiliary;
-
-import java.util.Properties;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-import org.apache.commons.jcs.utils.config.OptionConverter;
-import org.apache.commons.jcs.utils.config.PropertySetter;
-import org.apache.commons.jcs.utils.serialization.StandardSerializer;
-
-/**
- * Configuration util for auxiliary caches. I plan to move the auxiliary configuration from the
- * composite cache configurator here.
- */
-public class AuxiliaryCacheConfigurator
-{
-    /** The logger. */
-    private static final Log log = LogManager.getLog( AuxiliaryCacheConfigurator.class );
-
-    /** .attributes */
-    public static final String ATTRIBUTE_PREFIX = ".attributes";
-
-    /**
-     * jcs.auxiliary.NAME.cacheeventlogger=CLASSNAME
-     * <p>
-     * jcs.auxiliary.NAME.cacheeventlogger.attributes.CUSTOMPROPERTY=VALUE
-     */
-    public static final String CACHE_EVENT_LOGGER_PREFIX = ".cacheeventlogger";
-
-    /**
-     * jcs.auxiliary.NAME.serializer=CLASSNAME
-     * <p>
-     * jcs.auxiliary.NAME.serializer.attributes.CUSTOMPROPERTY=VALUE
-     */
-    public static final String SERIALIZER_PREFIX = ".serializer";
-
-    /**
-     * Parses the event logger config, if there is any for the auxiliary.
-     * <p>
-     * @param props
-     * @param auxPrefix - ex. AUXILIARY_PREFIX + auxName
-     * @return cacheEventLogger
-     */
-    public static ICacheEventLogger parseCacheEventLogger( Properties props, String auxPrefix )
-    {
-        ICacheEventLogger cacheEventLogger = null;
-
-        // auxFactory was not previously initialized.
-        String eventLoggerClassName = auxPrefix + CACHE_EVENT_LOGGER_PREFIX;
-        cacheEventLogger = OptionConverter.instantiateByKey( props, eventLoggerClassName, null );
-        if ( cacheEventLogger != null )
-        {
-            String cacheEventLoggerAttributePrefix = auxPrefix + CACHE_EVENT_LOGGER_PREFIX + ATTRIBUTE_PREFIX;
-            PropertySetter.setProperties( cacheEventLogger, props, cacheEventLoggerAttributePrefix + "." );
-            log.info( "Using custom cache event logger [{0}] for auxiliary [{1}]",
-                    cacheEventLogger, auxPrefix );
-        }
-        else
-        {
-            log.info( "No cache event logger defined for auxiliary [{0}]", auxPrefix );
-        }
-        return cacheEventLogger;
-    }
-
-    /**
-     * Parses the element config, if there is any for the auxiliary.
-     * <p>
-     * @param props
-     * @param auxPrefix - ex. AUXILIARY_PREFIX + auxName
-     * @return cacheEventLogger
-     */
-    public static IElementSerializer parseElementSerializer( Properties props, String auxPrefix )
-    {
-        // TODO take in the entire prop key
-        IElementSerializer elementSerializer = null;
-
-        // auxFactory was not previously initialized.
-        String elementSerializerClassName = auxPrefix + SERIALIZER_PREFIX;
-        elementSerializer = OptionConverter.instantiateByKey( props, elementSerializerClassName, null );
-        if ( elementSerializer != null )
-        {
-            String attributePrefix = auxPrefix + SERIALIZER_PREFIX + ATTRIBUTE_PREFIX;
-            PropertySetter.setProperties( elementSerializer, props, attributePrefix + "." );
-            log.info( "Using custom element serializer [{0}] for auxiliary [{1}]",
-                    elementSerializer, auxPrefix );
-        }
-        else
-        {
-            // use the default standard serializer
-            elementSerializer = new StandardSerializer();
-            log.info( "Using standard serializer [{0}] for auxiliary [{1}]",
-                    elementSerializer, auxPrefix );
-        }
-        return elementSerializer;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/AuxiliaryCacheFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/AuxiliaryCacheFactory.java
deleted file mode 100644
index 8175c4a..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/AuxiliaryCacheFactory.java
+++ /dev/null
@@ -1,71 +0,0 @@
-package org.apache.commons.jcs.auxiliary;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-
-/**
- * All auxiliary caches must have a factory that the cache configurator can use to create instances.
- */
-public interface AuxiliaryCacheFactory
-{
-    /**
-     * Creates an auxiliary using the supplied attributes. Adds it to the composite cache manager.
-     * 
-     * @param attr
-     * @param cacheMgr This allows auxiliaries to reference the manager without assuming that it is
-     *            a singleton. This will allow JCS to be a non-singleton. Also, it makes it easier to
-     *            test.
-     * @param cacheEventLogger
-     * @param elementSerializer
-     * @return AuxiliaryCache
-     * @throws Exception if cache instance could not be created
-     */
-    <K, V> AuxiliaryCache<K, V> createCache(
-            AuxiliaryCacheAttributes attr, ICompositeCacheManager cacheMgr,
-            ICacheEventLogger cacheEventLogger, IElementSerializer elementSerializer )
-            throws Exception;
-
-    /**
-     * Initialize this factory
-     */
-    void initialize();
-
-    /**
-     * Dispose of this factory, clean up shared resources
-     */
-    void dispose();
-
-    /**
-     * Sets the name attribute of the AuxiliaryCacheFactory object
-     * 
-     * @param s The new name value
-     */
-    void setName( String s );
-
-    /**
-     * Gets the name attribute of the AuxiliaryCacheFactory object
-     * 
-     * @return The name value
-     */
-    String getName();
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/AbstractDiskCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/AbstractDiskCache.java
deleted file mode 100644
index e82712f..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/AbstractDiskCache.java
+++ /dev/null
@@ -1,840 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-
-import org.apache.commons.jcs.auxiliary.AbstractAuxiliaryCacheEventLogging;
-import org.apache.commons.jcs.auxiliary.AuxiliaryCache;
-import org.apache.commons.jcs.auxiliary.disk.behavior.IDiskCacheAttributes;
-import org.apache.commons.jcs.engine.CacheEventQueueFactory;
-import org.apache.commons.jcs.engine.CacheInfo;
-import org.apache.commons.jcs.engine.CacheStatus;
-import org.apache.commons.jcs.engine.behavior.ICache;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheEventQueue;
-import org.apache.commons.jcs.engine.behavior.ICacheListener;
-import org.apache.commons.jcs.engine.stats.StatElement;
-import org.apache.commons.jcs.engine.stats.Stats;
-import org.apache.commons.jcs.engine.stats.behavior.IStatElement;
-import org.apache.commons.jcs.engine.stats.behavior.IStats;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-import org.apache.commons.jcs.utils.struct.LRUMap;
-
-/**
- * Abstract class providing a base implementation of a disk cache, which can be easily extended to
- * implement a disk cache for a specific persistence mechanism.
- *
- * When implementing the abstract methods note that while this base class handles most things, it
- * does not acquire or release any locks. Implementations should do so as necessary. This is mainly
- * done to minimize the time spent in critical sections.
- *
- * Error handling in this class needs to be addressed. Currently if an exception is thrown by the
- * persistence mechanism, this class destroys the event queue. Should it also destroy purgatory?
- * Should it dispose itself?
- */
-public abstract class AbstractDiskCache<K, V>
-    extends AbstractAuxiliaryCacheEventLogging<K, V>
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog( AbstractDiskCache.class );
-
-    /** Generic disk cache attributes */
-    private IDiskCacheAttributes diskCacheAttributes = null;
-
-    /**
-     * Map where elements are stored between being added to this cache and actually spooled to disk.
-     * This allows puts to the disk cache to return quickly, and the more expensive operation of
-     * serializing the elements to persistent storage queued for later.
-     *
-     * If the elements are pulled into the memory cache while the are still in purgatory, writing to
-     * disk can be canceled.
-     */
-    private Map<K, PurgatoryElement<K, V>> purgatory;
-
-    /**
-     * The CacheEventQueue where changes will be queued for asynchronous updating of the persistent
-     * storage.
-     */
-    private ICacheEventQueue<K, V> cacheEventQueue;
-
-    /**
-     * Indicates whether the cache is 'alive': initialized, but not yet disposed. Child classes must
-     * set this to true.
-     */
-    private boolean alive = false;
-
-    /** Every cache will have a name, subclasses must set this when they are initialized. */
-    private String cacheName;
-
-    /** DEBUG: Keeps a count of the number of purgatory hits for debug messages */
-    private int purgHits = 0;
-
-    /**
-     * We lock here, so that we cannot get an update after a remove all. an individual removal locks
-     * the item.
-     */
-    private final ReentrantReadWriteLock removeAllLock = new ReentrantReadWriteLock();
-
-    // ----------------------------------------------------------- constructors
-
-    /**
-     * Construct the abstract disk cache, create event queues and purgatory. Child classes should
-     * set the alive flag to true after they are initialized.
-     *
-     * @param attr
-     */
-    protected AbstractDiskCache( IDiskCacheAttributes attr )
-    {
-        this.diskCacheAttributes = attr;
-        this.cacheName = attr.getCacheName();
-
-        // create queue
-        CacheEventQueueFactory<K, V> fact = new CacheEventQueueFactory<>();
-        this.cacheEventQueue = fact.createCacheEventQueue( new MyCacheListener(), CacheInfo.listenerId, cacheName,
-                                                           diskCacheAttributes.getEventQueuePoolName(),
-                                                           diskCacheAttributes.getEventQueueType() );
-
-        // create purgatory
-        initPurgatory();
-    }
-
-    /**
-     * @return true if the cache is alive
-     */
-    public boolean isAlive()
-    {
-        return alive;
-    }
-
-    /**
-     * @param alive set the alive status
-     */
-    public void setAlive(boolean alive)
-    {
-        this.alive = alive;
-    }
-
-    /**
-     * Purgatory size of -1 means to use a HashMap with no size limit. Anything greater will use an
-     * LRU map of some sort.
-     *
-     * TODO Currently setting this to 0 will cause nothing to be put to disk, since it will assume
-     *       that if an item is not in purgatory, then it must have been plucked. We should make 0
-     *       work, a way to not use purgatory.
-     */
-    private void initPurgatory()
-    {
-        // we need this so we can stop the updates from happening after a
-        // removeall
-        removeAllLock.writeLock().lock();
-
-        try
-        {
-            synchronized (this)
-            {
-                if ( diskCacheAttributes.getMaxPurgatorySize() >= 0 )
-                {
-                    purgatory = new LRUMap<>( diskCacheAttributes.getMaxPurgatorySize() );
-                }
-                else
-                {
-                    purgatory = new HashMap<>();
-                }
-            }
-        }
-        finally
-        {
-            removeAllLock.writeLock().unlock();
-        }
-    }
-
-    // ------------------------------------------------------- interface ICache
-
-    /**
-     * Adds the provided element to the cache. Element will be added to purgatory, and then queued
-     * for later writing to the serialized storage mechanism.
-     *
-     * An update results in a put event being created. The put event will call the handlePut method
-     * defined here. The handlePut method calls the implemented doPut on the child.
-     *
-     * @param cacheElement
-     * @throws IOException
-     * @see org.apache.commons.jcs.engine.behavior.ICache#update
-     */
-    @Override
-    public final void update( ICacheElement<K, V> cacheElement )
-        throws IOException
-    {
-        log.debug( "Putting element in purgatory, cacheName: {0}, key: {1}",
-                () -> cacheName, () -> cacheElement.getKey() );
-
-        try
-        {
-            // Wrap the CacheElement in a PurgatoryElement
-            PurgatoryElement<K, V> pe = new PurgatoryElement<>( cacheElement );
-
-            // Indicates the the element is eligible to be spooled to disk,
-            // this will remain true unless the item is pulled back into
-            // memory.
-            pe.setSpoolable( true );
-
-            // Add the element to purgatory
-            synchronized ( purgatory )
-            {
-                purgatory.put( pe.getKey(), pe );
-            }
-
-            // Queue element for serialization
-            cacheEventQueue.addPutEvent( pe );
-        }
-        catch ( IOException ex )
-        {
-            log.error( "Problem adding put event to queue.", ex );
-
-            cacheEventQueue.destroy();
-        }
-    }
-
-    /**
-     * Check to see if the item is in purgatory. If so, return it. If not, check to see if we have
-     * it on disk.
-     *
-     * @param key
-     * @return ICacheElement&lt;K, V&gt; or null
-     * @see AuxiliaryCache#get
-     */
-    @Override
-    public final ICacheElement<K, V> get( K key )
-    {
-        // If not alive, always return null.
-
-        if ( !alive )
-        {
-            log.debug( "get was called, but the disk cache is not alive." );
-            return null;
-        }
-
-        PurgatoryElement<K, V> pe = null;
-        synchronized ( purgatory )
-        {
-            pe = purgatory.get( key );
-        }
-
-        // If the element was found in purgatory
-        if ( pe != null )
-        {
-            purgHits++;
-
-            if ( purgHits % 100 == 0 )
-            {
-                log.debug( "Purgatory hits = {0}", purgHits );
-            }
-
-            // Since the element will go back to the memory cache, we could set
-            // spoolable to false, which will prevent the queue listener from
-            // serializing the element. This would not match the disk cache
-            // behavior and the behavior of other auxiliaries. Gets never remove
-            // items from auxiliaries.
-            // Beyond consistency, the items should stay in purgatory and get
-            // spooled since the mem cache may be set to 0. If an item is
-            // active, it will keep getting put into purgatory and removed. The
-            // CompositeCache now does not put an item to memory from disk if
-            // the size is 0.
-            // Do not set spoolable to false. Just let it go to disk. This
-            // will allow the memory size = 0 setting to work well.
-
-            log.debug( "Found element in purgatory, cacheName: {0}, key: {1}",
-                    cacheName, key );
-
-            return pe.getCacheElement();
-        }
-
-        // If we reach this point, element was not found in purgatory, so get
-        // it from the cache.
-        try
-        {
-            return doGet( key );
-        }
-        catch ( Exception e )
-        {
-            log.error( e );
-
-            cacheEventQueue.destroy();
-        }
-
-        return null;
-    }
-
-    /**
-     * Gets items from the cache matching the given pattern. Items from memory will replace those
-     * from remote sources.
-     *
-     * This only works with string keys. It's too expensive to do a toString on every key.
-     *
-     * Auxiliaries will do their best to handle simple expressions. For instance, the JDBC disk
-     * cache will convert * to % and . to _
-     *
-     * @param pattern
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data matching the pattern.
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMatching( String pattern )
-        throws IOException
-    {
-        // Get the keys from purgatory
-        Set<K> keyArray = null;
-
-        // this avoids locking purgatory, but it uses more memory
-        synchronized ( purgatory )
-        {
-            keyArray = new HashSet<>(purgatory.keySet());
-        }
-
-        Set<K> matchingKeys = getKeyMatcher().getMatchingKeysFromArray( pattern, keyArray );
-
-        // call getMultiple with the set
-        Map<K, ICacheElement<K, V>> result = processGetMultiple( matchingKeys );
-
-        // Get the keys from disk
-        Map<K, ICacheElement<K, V>> diskMatches = doGetMatching( pattern );
-
-        result.putAll( diskMatches );
-
-        return result;
-    }
-
-    /**
-     * The keys in the cache.
-     *
-     * @see org.apache.commons.jcs.auxiliary.AuxiliaryCache#getKeySet()
-     */
-    @Override
-    public abstract Set<K> getKeySet() throws IOException;
-
-    /**
-     * Removes are not queued. A call to remove is immediate.
-     *
-     * @param key
-     * @return whether the item was present to be removed.
-     * @throws IOException
-     * @see org.apache.commons.jcs.engine.behavior.ICache#remove
-     */
-    @Override
-    public final boolean remove( K key )
-        throws IOException
-    {
-        PurgatoryElement<K, V> pe = null;
-
-        synchronized ( purgatory )
-        {
-            // I'm getting the object, so I can lock on the element
-            // Remove element from purgatory if it is there
-            pe = purgatory.get( key );
-        }
-
-        if ( pe != null )
-        {
-            synchronized ( pe.getCacheElement() )
-            {
-                synchronized ( purgatory )
-                {
-                    purgatory.remove( key );
-                }
-
-                // no way to remove from queue, just make sure it doesn't get on
-                // disk and then removed right afterwards
-                pe.setSpoolable( false );
-
-                // Remove from persistent store immediately
-                doRemove( key );
-            }
-        }
-        else
-        {
-            // Remove from persistent store immediately
-            doRemove( key );
-        }
-
-        return false;
-    }
-
-    /**
-     * @throws IOException
-     * @see org.apache.commons.jcs.engine.behavior.ICache#removeAll
-     */
-    @Override
-    public final void removeAll()
-        throws IOException
-    {
-        if ( this.diskCacheAttributes.isAllowRemoveAll() )
-        {
-            // Replace purgatory with a new empty hashtable
-            initPurgatory();
-
-            // Remove all from persistent store immediately
-            doRemoveAll();
-        }
-        else
-        {
-            log.info( "RemoveAll was requested but the request was not "
-                    + "fulfilled: allowRemoveAll is set to false." );
-        }
-    }
-
-    /**
-     * Adds a dispose request to the disk cache.
-     *
-     * Disposal proceeds in several steps.
-     * <ol>
-     * <li>Prior to this call the Composite cache dumped the memory into the disk cache. If it is
-     * large then we need to wait for the event queue to finish.</li>
-     * <li>Wait until the event queue is empty of until the configured ShutdownSpoolTimeLimit is
-     * reached.</li>
-     * <li>Call doDispose on the concrete impl.</li>
-     * </ol>
-     * @throws IOException
-     */
-    @Override
-    public final void dispose()
-        throws IOException
-    {
-        Thread t = new Thread(() ->
-        {
-            boolean keepGoing = true;
-            // long total = 0;
-            long interval = 100;
-            while ( keepGoing )
-            {
-                keepGoing = !cacheEventQueue.isEmpty();
-                try
-                {
-                    Thread.sleep( interval );
-                    // total += interval;
-                    // log.info( "total = " + total );
-                }
-                catch ( InterruptedException e )
-                {
-                    break;
-                }
-            }
-            log.info( "No longer waiting for event queue to finish: {0}",
-                    () -> cacheEventQueue.getStatistics() );
-        });
-        t.start();
-        // wait up to 60 seconds for dispose and then quit if not done.
-        try
-        {
-            t.join( this.diskCacheAttributes.getShutdownSpoolTimeLimit() * 1000L );
-        }
-        catch ( InterruptedException ex )
-        {
-            log.error( "The Shutdown Spool Process was interrupted.", ex );
-        }
-
-        log.info( "In dispose, destroying event queue." );
-        // This stops the processor thread.
-        cacheEventQueue.destroy();
-
-        // Invoke any implementation specific disposal code
-        // need to handle the disposal first.
-        doDispose();
-
-        alive = false;
-    }
-
-    /**
-     * @return the region name.
-     * @see ICache#getCacheName
-     */
-    @Override
-    public String getCacheName()
-    {
-        return cacheName;
-    }
-
-    /**
-     * Gets basic stats for the abstract disk cache.
-     *
-     * @return String
-     */
-    @Override
-    public String getStats()
-    {
-        return getStatistics().toString();
-    }
-
-    /**
-     * Returns semi-structured data.
-     *
-     * @see org.apache.commons.jcs.auxiliary.AuxiliaryCache#getStatistics()
-     */
-    @Override
-    public IStats getStatistics()
-    {
-        IStats stats = new Stats();
-        stats.setTypeName( "Abstract Disk Cache" );
-
-        ArrayList<IStatElement<?>> elems = new ArrayList<>();
-
-        elems.add(new StatElement<>( "Purgatory Hits", Integer.valueOf(purgHits) ) );
-        elems.add(new StatElement<>( "Purgatory Size", Integer.valueOf(purgatory.size()) ) );
-
-        // get the stats from the event queue too
-        IStats eqStats = this.cacheEventQueue.getStatistics();
-        elems.addAll(eqStats.getStatElements());
-
-        stats.setStatElements( elems );
-
-        return stats;
-    }
-
-    /**
-     * @return the status -- alive or disposed from CacheConstants
-     * @see ICache#getStatus
-     */
-    @Override
-    public CacheStatus getStatus()
-    {
-        return ( alive ? CacheStatus.ALIVE : CacheStatus.DISPOSED );
-    }
-
-    /**
-     * Size cannot be determined without knowledge of the cache implementation, so subclasses will
-     * need to implement this method.
-     *
-     * @return the number of items.
-     * @see ICache#getSize
-     */
-    @Override
-    public abstract int getSize();
-
-    /**
-     * @see org.apache.commons.jcs.engine.behavior.ICacheType#getCacheType
-     * @return Always returns DISK_CACHE since subclasses should all be of that type.
-     */
-    @Override
-    public CacheType getCacheType()
-    {
-        return CacheType.DISK_CACHE;
-    }
-
-    /**
-     * Cache that implements the CacheListener interface, and calls appropriate methods in its
-     * parent class.
-     */
-    protected class MyCacheListener
-        implements ICacheListener<K, V>
-    {
-        /** Id of the listener */
-        private long listenerId = 0;
-
-        /**
-         * @return cacheElement.getElementAttributes();
-         * @throws IOException
-         * @see ICacheListener#getListenerId
-         */
-        @Override
-        public long getListenerId()
-            throws IOException
-        {
-            return this.listenerId;
-        }
-
-        /**
-         * @param id
-         * @throws IOException
-         * @see ICacheListener#setListenerId
-         */
-        @Override
-        public void setListenerId( long id )
-            throws IOException
-        {
-            this.listenerId = id;
-        }
-
-        /**
-         * @param element
-         * @throws IOException
-         * @see ICacheListener#handlePut NOTE: This checks if the element is a puratory element and
-         *      behaves differently depending. However since we have control over how elements are
-         *      added to the cache event queue, that may not be needed ( they are always
-         *      PurgatoryElements ).
-         */
-        @Override
-        public void handlePut( ICacheElement<K, V> element )
-            throws IOException
-        {
-            if ( alive )
-            {
-                // If the element is a PurgatoryElement<K, V> we must check to see
-                // if it is still spoolable, and remove it from purgatory.
-                if ( element instanceof PurgatoryElement )
-                {
-                    PurgatoryElement<K, V> pe = (PurgatoryElement<K, V>) element;
-
-                    synchronized ( pe.getCacheElement() )
-                    {
-                        // TODO consider a timeout.
-                        // we need this so that we can have multiple update
-                        // threads and still have removeAll requests come in that
-                        // always win
-                        removeAllLock.readLock().lock();
-
-                        try
-                        {
-                            // TODO consider changing purgatory sync
-                            // String keyAsString = element.getKey().toString();
-                            synchronized ( purgatory )
-                            {
-                                // If the element has already been removed from
-                                // purgatory do nothing
-                                if ( !purgatory.containsKey( pe.getKey() ) )
-                                {
-                                    return;
-                                }
-
-                                element = pe.getCacheElement();
-                            }
-
-                            // I took this out of the purgatory sync block.
-                            // If the element is still eligible, spool it.
-                            if ( pe.isSpoolable() )
-                            {
-                                doUpdate( element );
-                            }
-                        }
-                        finally
-                        {
-                            removeAllLock.readLock().unlock();
-                        }
-
-                        synchronized ( purgatory )
-                        {
-                            // After the update has completed, it is safe to
-                            // remove the element from purgatory.
-                            purgatory.remove( element.getKey() );
-                        }
-                    }
-                }
-                else
-                {
-                    // call the child's implementation
-                    doUpdate( element );
-                }
-            }
-            else
-            {
-                /*
-                 * The cache is not alive, hence the element should be removed from purgatory. All
-                 * elements should be removed eventually. Perhaps, the alive check should have been
-                 * done before it went in the queue. This block handles the case where the disk
-                 * cache fails during normal operations.
-                 */
-                synchronized ( purgatory )
-                {
-                    purgatory.remove( element.getKey() );
-                }
-            }
-        }
-
-        /**
-         * @param cacheName
-         * @param key
-         * @throws IOException
-         * @see ICacheListener#handleRemove
-         */
-        @Override
-        public void handleRemove( String cacheName, K key )
-            throws IOException
-        {
-            if ( alive )
-            {
-                if ( doRemove( key ) )
-                {
-                    log.debug( "Element removed, key: " + key );
-                }
-            }
-        }
-
-        /**
-         * @param cacheName
-         * @throws IOException
-         * @see ICacheListener#handleRemoveAll
-         */
-        @Override
-        public void handleRemoveAll( String cacheName )
-            throws IOException
-        {
-            if ( alive )
-            {
-                doRemoveAll();
-            }
-        }
-
-        /**
-         * @param cacheName
-         * @throws IOException
-         * @see ICacheListener#handleDispose
-         */
-        @Override
-        public void handleDispose( String cacheName )
-            throws IOException
-        {
-            if ( alive )
-            {
-                doDispose();
-            }
-        }
-    }
-
-    /**
-     * Before the event logging layer, the subclasses implemented the do* methods. Now the do*
-     * methods call the *WithEventLogging method on the super. The *WithEventLogging methods call
-     * the abstract process* methods. The children implement the process methods.
-     *
-     * ex. doGet calls getWithEventLogging, which calls processGet
-     */
-
-    /**
-     * Get a value from the persistent store.
-     *
-     * Before the event logging layer, the subclasses implemented the do* methods. Now the do*
-     * methods call the *EventLogging method on the super. The *WithEventLogging methods call the
-     * abstract process* methods. The children implement the process methods.
-     *
-     * @param key Key to locate value for.
-     * @return An object matching key, or null.
-     * @throws IOException
-     */
-    protected final ICacheElement<K, V> doGet( K key )
-        throws IOException
-    {
-        return super.getWithEventLogging( key );
-    }
-
-    /**
-     * Get a value from the persistent store.
-     *
-     * Before the event logging layer, the subclasses implemented the do* methods. Now the do*
-     * methods call the *EventLogging method on the super. The *WithEventLogging methods call the
-     * abstract process* methods. The children implement the process methods.
-     *
-     * @param pattern Used to match keys.
-     * @return A map of matches..
-     * @throws IOException
-     */
-    protected final Map<K, ICacheElement<K, V>> doGetMatching( String pattern )
-        throws IOException
-    {
-        return super.getMatchingWithEventLogging( pattern );
-    }
-
-    /**
-     * Add a cache element to the persistent store.
-     *
-     * Before the event logging layer, the subclasses implemented the do* methods. Now the do*
-     * methods call the *EventLogging method on the super. The *WithEventLogging methods call the
-     * abstract process* methods. The children implement the process methods.
-     *
-     * @param cacheElement
-     * @throws IOException
-     */
-    protected final void doUpdate( ICacheElement<K, V> cacheElement )
-        throws IOException
-    {
-        super.updateWithEventLogging( cacheElement );
-    }
-
-    /**
-     * Remove an object from the persistent store if found.
-     *
-     * Before the event logging layer, the subclasses implemented the do* methods. Now the do*
-     * methods call the *EventLogging method on the super. The *WithEventLogging methods call the
-     * abstract process* methods. The children implement the process methods.
-     *
-     * @param key Key of object to remove.
-     * @return whether or no the item was present when removed
-     * @throws IOException
-     */
-    protected final boolean doRemove( K key )
-        throws IOException
-    {
-        return super.removeWithEventLogging( key );
-    }
-
-    /**
-     * Remove all objects from the persistent store.
-     *
-     * Before the event logging layer, the subclasses implemented the do* methods. Now the do*
-     * methods call the *EventLogging method on the super. The *WithEventLogging methods call the
-     * abstract process* methods. The children implement the process methods.
-     *
-     * @throws IOException
-     */
-    protected final void doRemoveAll()
-        throws IOException
-    {
-        super.removeAllWithEventLogging();
-    }
-
-    /**
-     * Dispose of the persistent store. Note that disposal of purgatory and setting alive to false
-     * does NOT need to be done by this method.
-     *
-     * Before the event logging layer, the subclasses implemented the do* methods. Now the do*
-     * methods call the *EventLogging method on the super. The *WithEventLogging methods call the
-     * abstract process* methods. The children implement the process methods.
-     *
-     * @throws IOException
-     */
-    protected final void doDispose()
-        throws IOException
-    {
-        super.disposeWithEventLogging();
-    }
-
-    /**
-     * Gets the extra info for the event log.
-     *
-     * @return disk location
-     */
-    @Override
-    public String getEventLoggingExtraInfo()
-    {
-        return getDiskLocation();
-    }
-
-    /**
-     * This is used by the event logging.
-     *
-     * @return the location of the disk, either path or ip.
-     */
-    protected abstract String getDiskLocation();
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/AbstractDiskCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/AbstractDiskCacheAttributes.java
deleted file mode 100644
index da5213b..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/AbstractDiskCacheAttributes.java
+++ /dev/null
@@ -1,221 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.File;
-
-import org.apache.commons.jcs.auxiliary.AbstractAuxiliaryCacheAttributes;
-import org.apache.commons.jcs.auxiliary.disk.behavior.IDiskCacheAttributes;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * This has common attributes that any conceivable disk cache would need.
- */
-public abstract class AbstractDiskCacheAttributes extends AbstractAuxiliaryCacheAttributes implements IDiskCacheAttributes
-{
-    /** Don't change. */
-    private static final long serialVersionUID = 8306631920391711229L;
-
-    /** The logger */
-    private static final Log log = LogManager.getLog(AbstractDiskCacheAttributes.class);
-
-    /** path to disk */
-    private File diskPath;
-
-    /** if this is false, we will not execute remove all */
-    private boolean allowRemoveAll = true;
-
-    /** default to 5000 */
-    private int maxPurgatorySize = MAX_PURGATORY_SIZE_DEFAULT;
-
-    /** Default amount of time to allow for key persistence on shutdown */
-    private static final int DEFAULT_shutdownSpoolTimeLimit = 60;
-
-    /**
-     * This default determines how long the shutdown will wait for the key spool and data defrag to
-     * finish.
-     */
-    private int shutdownSpoolTimeLimit = DEFAULT_shutdownSpoolTimeLimit;
-
-    /** Type of disk limit: SIZE or COUNT */
-    private DiskLimitType diskLimitType = DiskLimitType.COUNT;
-
-    /**
-     * Sets the diskPath attribute of the DiskCacheAttributes object
-     * <p>
-     *
-     * @param path
-     *            The new diskPath value
-     */
-    @Override
-    public void setDiskPath(String path)
-    {
-        setDiskPath(new File(path));
-    }
-
-    /**
-     * Sets the diskPath attribute of the DiskCacheAttributes object
-     * <p>
-     *
-     * @param diskPath
-     *            The new diskPath value
-     */
-    public void setDiskPath(File diskPath)
-    {
-        this.diskPath = diskPath;
-        boolean result = this.diskPath.isDirectory();
-
-        if (!result)
-        {
-            result = this.diskPath.mkdirs();
-        }
-        if (!result)
-        {
-            log.error("Failed to create directory {0}", diskPath);
-        }
-    }
-
-    /**
-     * Gets the diskPath attribute of the attributes object
-     * <p>
-     *
-     * @return The diskPath value
-     */
-    @Override
-    public File getDiskPath()
-    {
-        return this.diskPath;
-    }
-
-    /**
-     * Gets the maxKeySize attribute of the DiskCacheAttributes object
-     * <p>
-     *
-     * @return The maxPurgatorySize value
-     */
-    @Override
-    public int getMaxPurgatorySize()
-    {
-        return maxPurgatorySize;
-    }
-
-    /**
-     * Sets the maxPurgatorySize attribute of the DiskCacheAttributes object
-     * <p>
-     *
-     * @param maxPurgatorySize
-     *            The new maxPurgatorySize value
-     */
-    @Override
-    public void setMaxPurgatorySize(int maxPurgatorySize)
-    {
-        this.maxPurgatorySize = maxPurgatorySize;
-    }
-
-    /**
-     * Get the amount of time in seconds we will wait for elements to move to disk during shutdown
-     * for a particular region.
-     * <p>
-     *
-     * @return the time in seconds.
-     */
-    @Override
-    public int getShutdownSpoolTimeLimit()
-    {
-        return this.shutdownSpoolTimeLimit;
-    }
-
-    /**
-     * Sets the amount of time in seconds we will wait for elements to move to disk during shutdown
-     * for a particular region.
-     * <p>
-     * This is how long we give the event queue to empty.
-     * <p>
-     * The default is 60 seconds.
-     * <p>
-     *
-     * @param shutdownSpoolTimeLimit
-     *            the time in seconds
-     */
-    @Override
-    public void setShutdownSpoolTimeLimit(int shutdownSpoolTimeLimit)
-    {
-        this.shutdownSpoolTimeLimit = shutdownSpoolTimeLimit;
-    }
-
-    /**
-     * @param allowRemoveAll
-     *            The allowRemoveAll to set.
-     */
-    @Override
-    public void setAllowRemoveAll(boolean allowRemoveAll)
-    {
-        this.allowRemoveAll = allowRemoveAll;
-    }
-
-    /**
-     * @return Returns the allowRemoveAll.
-     */
-    @Override
-    public boolean isAllowRemoveAll()
-    {
-        return allowRemoveAll;
-    }
-
-    /**
-     * Includes the common attributes for a debug message.
-     * <p>
-     *
-     * @return String
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder str = new StringBuilder();
-        str.append("AbstractDiskCacheAttributes ");
-        str.append("\n diskPath = " + getDiskPath());
-        str.append("\n maxPurgatorySize   = " + getMaxPurgatorySize());
-        str.append("\n allowRemoveAll   = " + isAllowRemoveAll());
-        str.append("\n ShutdownSpoolTimeLimit   = " + getShutdownSpoolTimeLimit());
-        return str.toString();
-    }
-
-    @Override
-    public void setDiskLimitType(DiskLimitType diskLimitType)
-    {
-        this.diskLimitType = diskLimitType;
-    }
-
-    @Override
-    public void setDiskLimitTypeName(String diskLimitTypeName)
-    {
-        if (diskLimitTypeName != null)
-        {
-            diskLimitType = DiskLimitType.valueOf(diskLimitTypeName.trim());
-        }
-    }
-
-    @Override
-    public DiskLimitType getDiskLimitType()
-    {
-        return diskLimitType;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/PurgatoryElement.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/PurgatoryElement.java
deleted file mode 100644
index db257bd..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/PurgatoryElement.java
+++ /dev/null
@@ -1,156 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-
-/**
- * Implementation of cache elements in purgatory.
- * 
- * Elements are stored in purgatory when they are spooled to the auxiliary cache, but have not yet
- * been written to disk.
- */
-public class PurgatoryElement<K, V>
-    extends CacheElement<K, V>
-{
-    /** Don't change */
-    private static final long serialVersionUID = -8152034342684135628L;
-
-    /** Is the element ready to be spooled? */
-    private boolean spoolable = false;
-
-    /** Wrapped cache Element */
-    private ICacheElement<K, V> cacheElement;
-
-    /**
-     * Constructor for the PurgatoryElement&lt;K, V&gt; object
-     * 
-     * @param cacheElement CacheElement
-     */
-    public PurgatoryElement( ICacheElement<K, V> cacheElement )
-    {
-        super(cacheElement.getCacheName(),
-                cacheElement.getKey(), cacheElement.getVal(),
-                cacheElement.getElementAttributes());
-        this.cacheElement = cacheElement;
-    }
-
-    /**
-     * Gets the spoolable property.
-     * 
-     * @return The spoolable value
-     */
-    public boolean isSpoolable()
-    {
-        return spoolable;
-    }
-
-    /**
-     * Sets the spoolable property.
-     * 
-     * @param spoolable The new spoolable value
-     */
-    public void setSpoolable( boolean spoolable )
-    {
-        this.spoolable = spoolable;
-    }
-
-    /**
-     * Get the wrapped cache element.
-     * 
-     * @return ICacheElement
-     */
-    public ICacheElement<K, V> getCacheElement()
-    {
-        return cacheElement;
-    }
-
-    // ------------------------------------------------ interface ICacheElement
-
-    /**
-     * @return cacheElement.getCacheName();
-     * @see ICacheElement#getCacheName
-     */
-    @Override
-    public String getCacheName()
-    {
-        return cacheElement.getCacheName();
-    }
-
-    /**
-     * @return cacheElement.getKey();
-     * @see ICacheElement#getKey
-     */
-    @Override
-    public K getKey()
-    {
-        return cacheElement.getKey();
-    }
-
-    /**
-     * @return cacheElement.getVal();
-     * @see ICacheElement#getVal
-     */
-    @Override
-    public V getVal()
-    {
-        return cacheElement.getVal();
-    }
-
-    /**
-     * @return cacheElement.getElementAttributes();
-     * @see ICacheElement#getElementAttributes
-     */
-    @Override
-    public IElementAttributes getElementAttributes()
-    {
-        return cacheElement.getElementAttributes();
-    }
-
-    /**
-     * @param attr
-     * @see ICacheElement#setElementAttributes
-     */
-    @Override
-    public void setElementAttributes( IElementAttributes attr )
-    {
-        cacheElement.setElementAttributes( attr );
-    }
-
-    /**
-     * @return debug string
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder buf = new StringBuilder();
-        buf.append( "[PurgatoryElement: " );
-        buf.append( " isSpoolable = " + isSpoolable() );
-        buf.append( " CacheElement = " + getCacheElement() );
-        buf.append( " CacheName = " + getCacheName() );
-        buf.append( " Key = " + getKey() );
-        buf.append( " Value = " + getVal() );
-        buf.append( " ElementAttributes = " + getElementAttributes() );
-        buf.append( "]" );
-        return buf.toString();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/behavior/IDiskCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/behavior/IDiskCacheAttributes.java
deleted file mode 100644
index f7d32fd..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/behavior/IDiskCacheAttributes.java
+++ /dev/null
@@ -1,131 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes;
-
-import java.io.File;
-
-/**
- * Common disk cache attributes.
- */
-public interface IDiskCacheAttributes
-    extends AuxiliaryCacheAttributes
-{
-    enum DiskLimitType {
-        /** limit elements by count (default) */
-        COUNT,
-        /** limit elements by their size */
-        SIZE
-    }
-    /**
-     * This is the default purgatory size limit. Purgatory is the area where
-     * items to be spooled are temporarily stored. It basically provides access
-     * to items on the to-be-spooled queue.
-     */
-    int MAX_PURGATORY_SIZE_DEFAULT = 5000;
-
-    /**
-     * Sets the diskPath attribute of the IJISPCacheAttributes object
-     * <p>
-     * @param path
-     *            The new diskPath value
-     */
-    void setDiskPath( String path );
-
-    /**
-     * Gets the diskPath attribute of the attributes object
-     * <p>
-     * @return The diskPath value
-     */
-    File getDiskPath();
-
-    /**
-     * Gets the maxKeySize attribute of the DiskCacheAttributes object
-     * <p>
-     * @return The maxPurgatorySize value
-     */
-    int getMaxPurgatorySize();
-
-    /**
-     * Sets the maxPurgatorySize attribute of the DiskCacheAttributes object
-     * <p>
-     * @param maxPurgatorySize
-     *            The new maxPurgatorySize value
-     */
-    void setMaxPurgatorySize( int maxPurgatorySize );
-
-    /**
-     * Get the amount of time in seconds we will wait for elements to move to
-     * disk during shutdown for a particular region.
-     * <p>
-     * @return the time in seconds.
-     */
-    int getShutdownSpoolTimeLimit();
-
-    /**
-     * Sets the amount of time in seconds we will wait for elements to move to
-     * disk during shutdown for a particular region.
-     * <p>
-     * This is how long we give the event queue to empty.
-     * <p>
-     * The default is 60 seconds.
-     * <p>
-     * @param shutdownSpoolTimeLimit
-     *            the time in seconds
-     */
-    void setShutdownSpoolTimeLimit( int shutdownSpoolTimeLimit );
-
-    /**
-     * If this is true then remove all is not prohibited.
-     * <p>
-     * @return boolean
-     */
-    boolean isAllowRemoveAll();
-
-    /**
-     * If this is false, then remove all requests will not be honored.
-     * <p>
-     * This provides a safety mechanism for the persistent store.
-     * <p>
-     * @param allowRemoveAll
-     */
-    void setAllowRemoveAll( boolean allowRemoveAll );
-
-    /**
-     * set the type of the limit of the cache size
-     * @param diskLimitType COUNT - limit by count of the elements, SIZE, limit by sum of element's size
-     */
-    void setDiskLimitType(DiskLimitType diskLimitType);
-
-    /**
-     * Translates and stores String values  of DiskLimitType
-     *
-     * Allowed values: "COUNT" and "SIZE"
-     * @param diskLimitTypeName
-     */
-    void setDiskLimitTypeName(String diskLimitTypeName);
-
-    /**
-     *
-     * @return active DiskLimitType
-     */
-    DiskLimitType getDiskLimitType();
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDisk.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDisk.java
deleted file mode 100644
index e246770..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDisk.java
+++ /dev/null
@@ -1,517 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.block;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.nio.file.StandardOpenOption;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
-
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-import org.apache.commons.jcs.utils.serialization.StandardSerializer;
-
-/**
- * This class manages reading an writing data to disk. When asked to write a value, it returns a
- * block array. It can read an object from the block numbers in a byte array.
- * <p>
- * @author Aaron Smuts
- */
-public class BlockDisk implements AutoCloseable
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog(BlockDisk.class);
-
-    /** The size of the header that indicates the amount of data stored in an occupied block. */
-    public static final byte HEADER_SIZE_BYTES = 4;
-    // N.B. 4 bytes is the size used for ByteBuffer.putInt(int value) and ByteBuffer.getInt()
-
-    /** defaults to 4kb */
-    private static final int DEFAULT_BLOCK_SIZE_BYTES = 4 * 1024;
-
-    /** Size of the blocks */
-    private final int blockSizeBytes;
-
-    /**
-     * the total number of blocks that have been used. If there are no free, we will use this to
-     * calculate the position of the next block.
-     */
-    private final AtomicInteger numberOfBlocks = new AtomicInteger(0);
-
-    /** Empty blocks that can be reused. */
-    private final ConcurrentLinkedQueue<Integer> emptyBlocks = new ConcurrentLinkedQueue<>();
-
-    /** The serializer. */
-    private final IElementSerializer elementSerializer;
-
-    /** Location of the spot on disk */
-    private final String filepath;
-
-    /** File channel for multiple concurrent reads and writes */
-    private final FileChannel fc;
-
-    /** How many bytes have we put to disk */
-    private final AtomicLong putBytes = new AtomicLong(0);
-
-    /** How many items have we put to disk */
-    private final AtomicLong putCount = new AtomicLong(0);
-
-    /**
-     * Constructor for the Disk object
-     * <p>
-     * @param file
-     * @param elementSerializer
-     * @throws IOException
-     */
-    public BlockDisk(File file, IElementSerializer elementSerializer)
-        throws IOException
-    {
-        this(file, DEFAULT_BLOCK_SIZE_BYTES, elementSerializer);
-    }
-
-    /**
-     * Creates the file and set the block size in bytes.
-     * <p>
-     * @param file
-     * @param blockSizeBytes
-     * @throws IOException
-     */
-    public BlockDisk(File file, int blockSizeBytes)
-        throws IOException
-    {
-        this(file, blockSizeBytes, new StandardSerializer());
-    }
-
-    /**
-     * Creates the file and set the block size in bytes.
-     * <p>
-     * @param file
-     * @param blockSizeBytes
-     * @param elementSerializer
-     * @throws IOException
-     */
-    public BlockDisk(File file, int blockSizeBytes, IElementSerializer elementSerializer)
-        throws IOException
-    {
-        this.filepath = file.getAbsolutePath();
-        this.fc = FileChannel.open(file.toPath(),
-                StandardOpenOption.CREATE,
-                StandardOpenOption.READ,
-                StandardOpenOption.WRITE);
-        this.numberOfBlocks.set((int) Math.ceil(1f * this.fc.size() / blockSizeBytes));
-
-        log.info("Constructing BlockDisk, blockSizeBytes [{0}]", blockSizeBytes);
-
-        this.blockSizeBytes = blockSizeBytes;
-        this.elementSerializer = elementSerializer;
-    }
-
-    /**
-     * Allocate a given number of blocks from the available set
-     *
-     * @param numBlocksNeeded
-     * @return an array of allocated blocks
-     */
-    private int[] allocateBlocks(int numBlocksNeeded)
-    {
-        assert numBlocksNeeded >= 1;
-
-        int[] blocks = new int[numBlocksNeeded];
-        // get them from the empty list or take the next one
-        for (int i = 0; i < numBlocksNeeded; i++)
-        {
-            Integer emptyBlock = emptyBlocks.poll();
-            if (emptyBlock == null)
-            {
-                emptyBlock = Integer.valueOf(numberOfBlocks.getAndIncrement());
-            }
-            blocks[i] = emptyBlock.intValue();
-        }
-
-        return blocks;
-    }
-
-    /**
-     * This writes an object to disk and returns the blocks it was stored in.
-     * <p>
-     * The program flow is as follows:
-     * <ol>
-     * <li>Serialize the object.</li>
-     * <li>Determine the number of blocks needed.</li>
-     * <li>Look for free blocks in the emptyBlock list.</li>
-     * <li>If there were not enough in the empty list. Take the nextBlock and increment it.</li>
-     * <li>If the data will not fit in one block, create sub arrays.</li>
-     * <li>Write the subarrays to disk.</li>
-     * <li>If the process fails we should decrement the block count if we took from it.</li>
-     * </ol>
-     * @param object
-     * @return the blocks we used.
-     * @throws IOException
-     */
-    protected <T> int[] write(T object)
-        throws IOException
-    {
-        // serialize the object
-        byte[] data = elementSerializer.serialize(object);
-
-        log.debug("write, total pre-chunking data.length = {0}", data.length);
-
-        this.putBytes.addAndGet(data.length);
-        this.putCount.incrementAndGet();
-
-        // figure out how many blocks we need.
-        int numBlocksNeeded = calculateTheNumberOfBlocksNeeded(data);
-
-        log.debug("numBlocksNeeded = {0}", numBlocksNeeded);
-
-        // allocate blocks
-        int[] blocks = allocateBlocks(numBlocksNeeded);
-
-        int offset = 0;
-        final int maxChunkSize = blockSizeBytes - HEADER_SIZE_BYTES;
-        ByteBuffer headerBuffer = ByteBuffer.allocate(HEADER_SIZE_BYTES);
-        ByteBuffer dataBuffer = ByteBuffer.wrap(data);
-
-        for (int i = 0; i < numBlocksNeeded; i++)
-        {
-            headerBuffer.clear();
-            int length = Math.min(maxChunkSize, data.length - offset);
-            headerBuffer.putInt(length);
-            headerBuffer.flip();
-
-            dataBuffer.position(offset).limit(offset + length);
-            ByteBuffer slice = dataBuffer.slice();
-
-            long position = calculateByteOffsetForBlockAsLong(blocks[i]);
-            // write the header
-            int written = fc.write(headerBuffer, position);
-            assert written == HEADER_SIZE_BYTES;
-
-            //write the data
-            written = fc.write(slice, position + HEADER_SIZE_BYTES);
-            assert written == length;
-
-            offset += length;
-        }
-
-        //fc.force(false);
-
-        return blocks;
-    }
-
-    /**
-     * Return the amount to put in each block. Fill them all the way, minus the header.
-     * <p>
-     * @param complete
-     * @param numBlocksNeeded
-     * @return byte[][]
-     */
-    protected byte[][] getBlockChunks(byte[] complete, int numBlocksNeeded)
-    {
-        byte[][] chunks = new byte[numBlocksNeeded][];
-
-        if (numBlocksNeeded == 1)
-        {
-            chunks[0] = complete;
-        }
-        else
-        {
-            int maxChunkSize = this.blockSizeBytes - HEADER_SIZE_BYTES;
-            int totalBytes = complete.length;
-            int totalUsed = 0;
-            for (short i = 0; i < numBlocksNeeded; i++)
-            {
-                // use the max that can be written to a block or whatever is left in the original
-                // array
-                int chunkSize = Math.min(maxChunkSize, totalBytes - totalUsed);
-                byte[] chunk = new byte[chunkSize];
-                // copy from the used position to the chunk size on the complete array to the chunk
-                // array.
-                System.arraycopy(complete, totalUsed, chunk, 0, chunkSize);
-                chunks[i] = chunk;
-                totalUsed += chunkSize;
-            }
-        }
-
-        return chunks;
-    }
-
-    /**
-     * Reads an object that is located in the specified blocks.
-     * <p>
-     * @param blockNumbers
-     * @return the object instance
-     * @throws IOException
-     * @throws ClassNotFoundException
-     */
-    protected <T> T read(int[] blockNumbers)
-        throws IOException, ClassNotFoundException
-    {
-        final ByteBuffer data;
-
-        if (blockNumbers.length == 1)
-        {
-            data = readBlock(blockNumbers[0]);
-        }
-        else
-        {
-            data = ByteBuffer.allocate(blockNumbers.length * getBlockSizeBytes());
-            // get all the blocks into data
-            for (short i = 0; i < blockNumbers.length; i++)
-            {
-                ByteBuffer chunk = readBlock(blockNumbers[i]);
-                data.put(chunk);
-            }
-
-            data.flip();
-        }
-
-        log.debug("read, total post combination data.length = {0}", () -> data.limit());
-
-        return elementSerializer.deSerialize(data.array(), null);
-    }
-
-    /**
-     * This reads the occupied data in a block.
-     * <p>
-     * The first four bytes of the record should tell us how long it is. The data is read into a
-     * byte array and then an object is constructed from the byte array.
-     * <p>
-     * @return byte[]
-     * @param block
-     * @throws IOException
-     */
-    private ByteBuffer readBlock(int block)
-        throws IOException
-    {
-        int datalen = 0;
-
-        String message = null;
-        boolean corrupted = false;
-        long fileLength = fc.size();
-
-        long position = calculateByteOffsetForBlockAsLong(block);
-//        if (position > fileLength)
-//        {
-//            corrupted = true;
-//            message = "Record " + position + " starts past EOF.";
-//        }
-//        else
-        {
-            ByteBuffer datalength = ByteBuffer.allocate(HEADER_SIZE_BYTES);
-            fc.read(datalength, position);
-            datalength.flip();
-            datalen = datalength.getInt();
-            if (position + datalen > fileLength)
-            {
-                corrupted = true;
-                message = "Record " + position + " exceeds file length.";
-            }
-        }
-
-        if (corrupted)
-        {
-            log.warn("\n The file is corrupt: \n {0}", message);
-            throw new IOException("The File Is Corrupt, need to reset");
-        }
-
-        ByteBuffer data = ByteBuffer.allocate(datalen);
-        fc.read(data, position + HEADER_SIZE_BYTES);
-        data.flip();
-
-        return data;
-    }
-
-    /**
-     * Add these blocks to the emptyBlock list.
-     * <p>
-     * @param blocksToFree
-     */
-    protected void freeBlocks(int[] blocksToFree)
-    {
-        if (blocksToFree != null)
-        {
-            for (short i = 0; i < blocksToFree.length; i++)
-            {
-                emptyBlocks.offer(Integer.valueOf(blocksToFree[i]));
-            }
-        }
-    }
-
-    /**
-     * Calculates the file offset for a particular block.
-     * <p>
-     * @param block number
-     * @return the byte offset for this block in the file as a long
-     * @since 2.0
-     */
-    protected long calculateByteOffsetForBlockAsLong(int block)
-    {
-        return (long) block * blockSizeBytes;
-    }
-
-    /**
-     * The number of blocks needed.
-     * <p>
-     * @param data
-     * @return the number of blocks needed to store the byte array
-     */
-    protected int calculateTheNumberOfBlocksNeeded(byte[] data)
-    {
-        int dataLength = data.length;
-
-        int oneBlock = blockSizeBytes - HEADER_SIZE_BYTES;
-
-        // takes care of 0 = HEADER_SIZE_BYTES + blockSizeBytes
-        if (dataLength <= oneBlock)
-        {
-            return 1;
-        }
-
-        int dividend = dataLength / oneBlock;
-
-        if (dataLength % oneBlock != 0)
-        {
-            dividend++;
-        }
-        return dividend;
-    }
-
-    /**
-     * Returns the file length.
-     * <p>
-     * @return the size of the file.
-     * @throws IOException
-     */
-    protected long length()
-        throws IOException
-    {
-        return fc.size();
-    }
-
-    /**
-     * Closes the file.
-     * <p>
-     * @throws IOException
-     */
-    @Override
-    public void close()
-        throws IOException
-    {
-        this.numberOfBlocks.set(0);
-        this.emptyBlocks.clear();
-        fc.close();
-    }
-
-    /**
-     * Resets the file.
-     * <p>
-     * @throws IOException
-     */
-    protected synchronized void reset()
-        throws IOException
-    {
-        this.numberOfBlocks.set(0);
-        this.emptyBlocks.clear();
-        fc.truncate(0);
-        fc.force(true);
-    }
-
-    /**
-     * @return Returns the numberOfBlocks.
-     */
-    protected int getNumberOfBlocks()
-    {
-        return numberOfBlocks.get();
-    }
-
-    /**
-     * @return Returns the blockSizeBytes.
-     */
-    protected int getBlockSizeBytes()
-    {
-        return blockSizeBytes;
-    }
-
-    /**
-     * @return Returns the average size of the an element inserted.
-     */
-    protected long getAveragePutSizeBytes()
-    {
-        long count = this.putCount.get();
-
-        if (count == 0)
-        {
-            return 0;
-        }
-        return this.putBytes.get() / count;
-    }
-
-    /**
-     * @return Returns the number of empty blocks.
-     */
-    protected int getEmptyBlocks()
-    {
-        return this.emptyBlocks.size();
-    }
-
-    /**
-     * For debugging only.
-     * <p>
-     * @return String with details.
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder buf = new StringBuilder();
-        buf.append("\nBlock Disk ");
-        buf.append("\n  Filepath [" + filepath + "]");
-        buf.append("\n  NumberOfBlocks [" + this.numberOfBlocks.get() + "]");
-        buf.append("\n  BlockSizeBytes [" + this.blockSizeBytes + "]");
-        buf.append("\n  Put Bytes [" + this.putBytes + "]");
-        buf.append("\n  Put Count [" + this.putCount + "]");
-        buf.append("\n  Average Size [" + getAveragePutSizeBytes() + "]");
-        buf.append("\n  Empty Blocks [" + this.getEmptyBlocks() + "]");
-        try
-        {
-            buf.append("\n  Length [" + length() + "]");
-        }
-        catch (IOException e)
-        {
-            // swallow
-        }
-        return buf.toString();
-    }
-
-    /**
-     * This is used for debugging.
-     * <p>
-     * @return the file path.
-     */
-    protected String getFilePath()
-    {
-        return filepath;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCache.java
deleted file mode 100644
index 1fd383c..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCache.java
+++ /dev/null
@@ -1,709 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.block;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-import java.util.stream.Collectors;
-
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes;
-import org.apache.commons.jcs.auxiliary.disk.AbstractDiskCache;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.engine.behavior.IRequireScheduler;
-import org.apache.commons.jcs.engine.control.group.GroupAttrName;
-import org.apache.commons.jcs.engine.control.group.GroupId;
-import org.apache.commons.jcs.engine.stats.StatElement;
-import org.apache.commons.jcs.engine.stats.Stats;
-import org.apache.commons.jcs.engine.stats.behavior.IStatElement;
-import org.apache.commons.jcs.engine.stats.behavior.IStats;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * There is one BlockDiskCache per region. It manages the key and data store.
- * <p>
- * @author Aaron Smuts
- */
-public class BlockDiskCache<K, V>
-    extends AbstractDiskCache<K, V>
-    implements IRequireScheduler
-{
-    /** The logger. */
-    private static final Log log = LogManager.getLog( BlockDiskCache.class );
-
-    /** The name to prefix all log messages with. */
-    private final String logCacheName;
-
-    /** The name of the file to store data. */
-    private final String fileName;
-
-    /** The data access object */
-    private BlockDisk dataFile;
-
-    /** Attributes governing the behavior of the block disk cache. */
-    private final BlockDiskCacheAttributes blockDiskCacheAttributes;
-
-    /** The root directory for keys and data. */
-    private final File rootDirectory;
-
-    /** Store, loads, and persists the keys */
-    private BlockDiskKeyStore<K> keyStore;
-
-    /**
-     * Use this lock to synchronize reads and writes to the underlying storage mechanism. We don't
-     * need a reentrant lock, since we only lock one level.
-     */
-    private final ReentrantReadWriteLock storageLock = new ReentrantReadWriteLock();
-
-    private ScheduledFuture<?> future;
-
-    /**
-     * Constructs the BlockDisk after setting up the root directory.
-     * <p>
-     * @param cacheAttributes
-     */
-    public BlockDiskCache( BlockDiskCacheAttributes cacheAttributes )
-    {
-        this( cacheAttributes, null );
-    }
-
-    /**
-     * Constructs the BlockDisk after setting up the root directory.
-     * <p>
-     * @param cacheAttributes
-     * @param elementSerializer used if supplied, the super's super will not set a null
-     */
-    public BlockDiskCache( BlockDiskCacheAttributes cacheAttributes, IElementSerializer elementSerializer )
-    {
-        super( cacheAttributes );
-        setElementSerializer( elementSerializer );
-
-        this.blockDiskCacheAttributes = cacheAttributes;
-        this.logCacheName = "Region [" + getCacheName() + "] ";
-
-        log.info("{0}: Constructing BlockDiskCache with attributes {1}", logCacheName, cacheAttributes );
-
-        // Make a clean file name
-        this.fileName = getCacheName().replaceAll("[^a-zA-Z0-9-_\\.]", "_");
-        this.rootDirectory = cacheAttributes.getDiskPath();
-
-        log.info("{0}: Cache file root directory: [{1}]", logCacheName, rootDirectory);
-
-        try
-        {
-            if ( this.blockDiskCacheAttributes.getBlockSizeBytes() > 0 )
-            {
-                this.dataFile = new BlockDisk( new File( rootDirectory, fileName + ".data" ),
-                                               this.blockDiskCacheAttributes.getBlockSizeBytes(),
-                                               getElementSerializer() );
-            }
-            else
-            {
-                this.dataFile = new BlockDisk( new File( rootDirectory, fileName + ".data" ),
-                                               getElementSerializer() );
-            }
-
-            keyStore = new BlockDiskKeyStore<>( this.blockDiskCacheAttributes, this );
-
-            boolean alright = verifyDisk();
-
-            if ( keyStore.size() == 0 || !alright )
-            {
-                this.reset();
-            }
-
-            // Initialization finished successfully, so set alive to true.
-            setAlive(true);
-            log.info("{0}: Block Disk Cache is alive.", logCacheName);
-        }
-        catch ( IOException e )
-        {
-            log.error("{0}: Failure initializing for fileName: {1} and root directory: {2}",
-                    logCacheName, fileName, rootDirectory, e);
-        }
-    }
-
-    /**
-     * @see org.apache.commons.jcs.engine.behavior.IRequireScheduler#setScheduledExecutorService(java.util.concurrent.ScheduledExecutorService)
-     */
-    @Override
-    public void setScheduledExecutorService(ScheduledExecutorService scheduledExecutor)
-    {
-        // add this region to the persistence thread.
-        // TODO we might need to stagger this a bit.
-        if ( this.blockDiskCacheAttributes.getKeyPersistenceIntervalSeconds() > 0 )
-        {
-            future = scheduledExecutor.scheduleAtFixedRate(keyStore::saveKeys,
-                    this.blockDiskCacheAttributes.getKeyPersistenceIntervalSeconds(),
-                    this.blockDiskCacheAttributes.getKeyPersistenceIntervalSeconds(),
-                    TimeUnit.SECONDS);
-        }
-    }
-
-    /**
-     * We need to verify that the file on disk uses the same block size and that the file is the
-     * proper size.
-     * <p>
-     * @return true if it looks ok
-     */
-    protected boolean verifyDisk()
-    {
-        boolean alright = false;
-        // simply try to read a few. If it works, then the file is probably ok.
-        // TODO add more.
-
-        storageLock.readLock().lock();
-
-        try
-        {
-            this.keyStore.entrySet().stream()
-                .limit(100)
-                .forEach(entry -> {
-                    try
-                    {
-                        Object data = this.dataFile.read(entry.getValue());
-                        if ( data == null )
-                        {
-                            throw new IOException("Data is null");
-                        }
-                    }
-                    catch (IOException | ClassNotFoundException e)
-                    {
-                        throw new RuntimeException(logCacheName
-                                + " Couldn't find data for key [" + entry.getKey() + "]", e);
-                    }
-                });
-            alright = true;
-        }
-        catch ( Exception e )
-        {
-            log.warn("{0}: Problem verifying disk.", logCacheName, e);
-            alright = false;
-        }
-        finally
-        {
-            storageLock.readLock().unlock();
-        }
-
-        return alright;
-    }
-
-    /**
-     * Return the keys in this cache.
-     * <p>
-     * @see org.apache.commons.jcs.auxiliary.disk.AbstractDiskCache#getKeySet()
-     */
-    @Override
-    public Set<K> getKeySet() throws IOException
-    {
-        HashSet<K> keys = new HashSet<>();
-
-        storageLock.readLock().lock();
-
-        try
-        {
-            keys.addAll(this.keyStore.keySet());
-        }
-        finally
-        {
-            storageLock.readLock().unlock();
-        }
-
-        return keys;
-    }
-
-    /**
-     * Gets matching items from the cache.
-     * <p>
-     * @param pattern
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache matching keys
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> processGetMatching( String pattern )
-    {
-        Set<K> keyArray = null;
-        storageLock.readLock().lock();
-        try
-        {
-            keyArray = new HashSet<>(keyStore.keySet());
-        }
-        finally
-        {
-            storageLock.readLock().unlock();
-        }
-
-        Set<K> matchingKeys = getKeyMatcher().getMatchingKeysFromArray( pattern, keyArray );
-
-        Map<K, ICacheElement<K, V>> elements = matchingKeys.stream()
-            .collect(Collectors.toMap(
-                    key -> key,
-                    key -> processGet( key ))).entrySet().stream()
-                .filter(entry -> entry.getValue() != null)
-                .collect(Collectors.toMap(
-                        entry -> entry.getKey(),
-                        entry -> entry.getValue()));
-
-        return elements;
-    }
-
-    /**
-     * Returns the number of keys.
-     * <p>
-     * (non-Javadoc)
-     * @see org.apache.commons.jcs.auxiliary.disk.AbstractDiskCache#getSize()
-     */
-    @Override
-    public int getSize()
-    {
-        return this.keyStore.size();
-    }
-
-    /**
-     * Gets the ICacheElement&lt;K, V&gt; for the key if it is in the cache. The program flow is as follows:
-     * <ol>
-     * <li>Make sure the disk cache is alive.</li> <li>Get a read lock.</li> <li>See if the key is
-     * in the key store.</li> <li>If we found a key, ask the BlockDisk for the object at the
-     * blocks..</li> <li>Release the lock.</li>
-     * </ol>
-     * @param key
-     * @return ICacheElement
-     * @see org.apache.commons.jcs.auxiliary.disk.AbstractDiskCache#get(Object)
-     */
-    @Override
-    protected ICacheElement<K, V> processGet( K key )
-    {
-        if ( !isAlive() )
-        {
-            log.debug("{0}: No longer alive so returning null for key = {1}", logCacheName, key );
-            return null;
-        }
-
-        log.debug("{0}: Trying to get from disk: {1}", logCacheName, key );
-
-        ICacheElement<K, V> object = null;
-
-
-        try
-        {
-            storageLock.readLock().lock();
-            try {
-                int[] ded = this.keyStore.get( key );
-                if ( ded != null )
-                {
-                    object = this.dataFile.read( ded );
-                }
-            } finally {
-                storageLock.readLock().unlock();
-            }
-
-        }
-        catch ( IOException ioe )
-        {
-            log.error("{0}: Failure getting from disk--IOException, key = {1}", logCacheName, key, ioe );
-            reset();
-        }
-        catch ( Exception e )
-        {
-            log.error("{0}: Failure getting from disk, key = {1}", logCacheName, key, e );
-        }
-        return object;
-    }
-
-    /**
-     * Writes an element to disk. The program flow is as follows:
-     * <ol>
-     * <li>Acquire write lock.</li> <li>See id an item exists for this key.</li> <li>If an item
-     * already exists, add its blocks to the remove list.</li> <li>Have the Block disk write the
-     * item.</li> <li>Create a descriptor and add it to the key map.</li> <li>Release the write
-     * lock.</li>
-     * </ol>
-     * @param element
-     * @see org.apache.commons.jcs.auxiliary.disk.AbstractDiskCache#update(ICacheElement)
-     */
-    @Override
-    protected void processUpdate( ICacheElement<K, V> element )
-    {
-        if ( !isAlive() )
-        {
-            log.debug("{0}: No longer alive; aborting put of key = {1}",
-                    () -> logCacheName, () -> element.getKey());
-            return;
-        }
-
-        int[] old = null;
-
-        // make sure this only locks for one particular cache region
-        storageLock.writeLock().lock();
-
-        try
-        {
-            old = this.keyStore.get( element.getKey() );
-
-            if ( old != null )
-            {
-                this.dataFile.freeBlocks( old );
-            }
-
-            int[] blocks = this.dataFile.write( element );
-
-            this.keyStore.put( element.getKey(), blocks );
-
-            log.debug("{0}: Put to file [{1}] key [{2}]", () -> logCacheName,
-                    () -> fileName, () -> element.getKey());
-        }
-        catch ( IOException e )
-        {
-            log.error("{0}: Failure updating element, key: {1} old: {2}",
-                    logCacheName, element.getKey(), Arrays.toString(old), e);
-        }
-        finally
-        {
-            storageLock.writeLock().unlock();
-        }
-
-        log.debug("{0}: Storing element on disk, key: {1}", () -> logCacheName,
-                () -> element.getKey() );
-    }
-
-    /**
-     * Returns true if the removal was successful; or false if there is nothing to remove. Current
-     * implementation always result in a disk orphan.
-     * <p>
-     * @param key
-     * @return true if removed anything
-     * @see org.apache.commons.jcs.auxiliary.disk.AbstractDiskCache#remove(Object)
-     */
-    @Override
-    protected boolean processRemove( K key )
-    {
-        if ( !isAlive() )
-        {
-            log.debug("{0}: No longer alive so returning false for key = {1}", logCacheName, key );
-            return false;
-        }
-
-        boolean reset = false;
-        boolean removed = false;
-
-        storageLock.writeLock().lock();
-
-        try
-        {
-            if (key instanceof String && key.toString().endsWith(NAME_COMPONENT_DELIMITER))
-            {
-                removed = performPartialKeyRemoval((String) key);
-            }
-            else if (key instanceof GroupAttrName && ((GroupAttrName<?>) key).attrName == null)
-            {
-                removed = performGroupRemoval(((GroupAttrName<?>) key).groupId);
-            }
-            else
-            {
-                removed = performSingleKeyRemoval(key);
-            }
-        }
-        catch ( Exception e )
-        {
-            log.error("{0}: Problem removing element.", logCacheName, e );
-            reset = true;
-        }
-        finally
-        {
-            storageLock.writeLock().unlock();
-        }
-
-        if ( reset )
-        {
-            reset();
-        }
-
-        return removed;
-    }
-
-    /**
-     * Remove all elements from the group. This does not use the iterator to remove. It builds a
-     * list of group elements and then removes them one by one.
-     * <p>
-     * This operates under a lock obtained in doRemove().
-     * <p>
-     *
-     * @param key
-     * @return true if an element was removed
-     */
-    private boolean performGroupRemoval(GroupId key)
-    {
-        // remove all keys of the same name group.
-        List<K> itemsToRemove = keyStore.keySet()
-                .stream()
-                .filter(k -> k instanceof GroupAttrName && ((GroupAttrName<?>) k).groupId.equals(key))
-                .collect(Collectors.toList());
-
-        // remove matches.
-        // Don't add to recycle bin here
-        // https://issues.apache.org/jira/browse/JCS-67
-        itemsToRemove.forEach(fullKey -> performSingleKeyRemoval(fullKey));
-        // TODO this needs to update the remove count separately
-
-        return !itemsToRemove.isEmpty();
-    }
-
-    /**
-     * Iterates over the keyset. Builds a list of matches. Removes all the keys in the list. Does
-     * not remove via the iterator, since the map impl may not support it.
-     * <p>
-     * This operates under a lock obtained in doRemove().
-     * <p>
-     *
-     * @param key
-     * @return true if there was a match
-     */
-    private boolean performPartialKeyRemoval(String key)
-    {
-        // remove all keys of the same name hierarchy.
-        List<K> itemsToRemove = keyStore.keySet()
-                .stream()
-                .filter(k -> k instanceof String && k.toString().startsWith(key))
-                .collect(Collectors.toList());
-
-        // remove matches.
-        // Don't add to recycle bin here
-        // https://issues.apache.org/jira/browse/JCS-67
-        itemsToRemove.forEach(fullKey -> performSingleKeyRemoval(fullKey));
-        // TODO this needs to update the remove count separately
-
-        return !itemsToRemove.isEmpty();
-    }
-
-
-	private boolean performSingleKeyRemoval(K key) {
-		boolean removed;
-		// remove single item.
-		int[] ded = this.keyStore.remove( key );
-		removed = ded != null;
-		if ( removed )
-		{
-		    this.dataFile.freeBlocks( ded );
-		}
-
-	    log.debug("{0}: Disk removal: Removed from key hash, key [{1}] removed = {2}",
-	            logCacheName, key, removed);
-		return removed;
-	}
-
-    /**
-     * Resets the keyfile, the disk file, and the memory key map.
-     * <p>
-     * @see org.apache.commons.jcs.auxiliary.disk.AbstractDiskCache#removeAll()
-     */
-    @Override
-    protected void processRemoveAll()
-    {
-        reset();
-    }
-
-    /**
-     * Dispose of the disk cache in a background thread. Joins against this thread to put a cap on
-     * the disposal time.
-     * <p>
-     * TODO make dispose window configurable.
-     */
-    @Override
-    public void processDispose()
-    {
-        Thread t = new Thread(this::disposeInternal, "BlockDiskCache-DisposalThread" );
-        t.start();
-        // wait up to 60 seconds for dispose and then quit if not done.
-        try
-        {
-            t.join( 60 * 1000 );
-        }
-        catch ( InterruptedException ex )
-        {
-            log.error("{0}: Interrupted while waiting for disposal thread to finish.",
-                    logCacheName, ex );
-        }
-    }
-
-    /**
-     * Internal method that handles the disposal.
-     */
-    protected void disposeInternal()
-    {
-        if ( !isAlive() )
-        {
-            log.error("{0}: Not alive and dispose was called, filename: {1}", logCacheName, fileName);
-            return;
-        }
-        storageLock.writeLock().lock();
-        try
-        {
-            // Prevents any interaction with the cache while we're shutting down.
-            setAlive(false);
-            this.keyStore.saveKeys();
-
-            if (future != null)
-            {
-                future.cancel(true);
-            }
-
-            try
-            {
-                log.debug("{0}: Closing files, base filename: {1}", logCacheName, fileName );
-                dataFile.close();
-                // dataFile = null;
-
-                // TOD make a close
-                // keyFile.close();
-                // keyFile = null;
-            }
-            catch ( IOException e )
-            {
-                log.error("{0}: Failure closing files in dispose, filename: {1}",
-                        logCacheName, fileName, e );
-            }
-        }
-        finally
-        {
-            storageLock.writeLock().unlock();
-        }
-
-        log.info("{0}: Shutdown complete.", logCacheName);
-    }
-
-    /**
-     * Returns the attributes.
-     * <p>
-     * @see org.apache.commons.jcs.auxiliary.AuxiliaryCache#getAuxiliaryCacheAttributes()
-     */
-    @Override
-    public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
-    {
-        return this.blockDiskCacheAttributes;
-    }
-
-    /**
-     * Reset effectively clears the disk cache, creating new files, recyclebins, and keymaps.
-     * <p>
-     * It can be used to handle errors by last resort, force content update, or removeall.
-     */
-    private void reset()
-    {
-        log.info("{0}: Resetting cache", logCacheName);
-
-        try
-        {
-            storageLock.writeLock().lock();
-
-            this.keyStore.reset();
-
-            if ( dataFile != null )
-            {
-                dataFile.reset();
-            }
-        }
-        catch ( IOException e )
-        {
-            log.error("{0}: Failure resetting state", logCacheName, e );
-        }
-        finally
-        {
-            storageLock.writeLock().unlock();
-        }
-    }
-
-    /**
-     * Add these blocks to the emptyBlock list.
-     * <p>
-     * @param blocksToFree
-     */
-    protected void freeBlocks( int[] blocksToFree )
-    {
-        this.dataFile.freeBlocks( blocksToFree );
-    }
-
-    /**
-     * Returns info about the disk cache.
-     * <p>
-     * @see org.apache.commons.jcs.auxiliary.AuxiliaryCache#getStatistics()
-     */
-    @Override
-    public IStats getStatistics()
-    {
-        IStats stats = new Stats();
-        stats.setTypeName( "Block Disk Cache" );
-
-        ArrayList<IStatElement<?>> elems = new ArrayList<>();
-
-        elems.add(new StatElement<>( "Is Alive", Boolean.valueOf(isAlive()) ) );
-        elems.add(new StatElement<>( "Key Map Size", Integer.valueOf(this.keyStore.size()) ) );
-
-        if (this.dataFile != null)
-        {
-            try
-            {
-                elems.add(new StatElement<>( "Data File Length", Long.valueOf(this.dataFile.length()) ) );
-            }
-            catch ( IOException e )
-            {
-                log.error( e );
-            }
-
-            elems.add(new StatElement<>( "Block Size Bytes",
-                    Integer.valueOf(this.dataFile.getBlockSizeBytes()) ) );
-            elems.add(new StatElement<>( "Number Of Blocks",
-                    Integer.valueOf(this.dataFile.getNumberOfBlocks()) ) );
-            elems.add(new StatElement<>( "Average Put Size Bytes",
-                    Long.valueOf(this.dataFile.getAveragePutSizeBytes()) ) );
-            elems.add(new StatElement<>( "Empty Blocks",
-                    Integer.valueOf(this.dataFile.getEmptyBlocks()) ) );
-        }
-
-        // get the stats from the super too
-        IStats sStats = super.getStatistics();
-        elems.addAll(sStats.getStatElements());
-
-        stats.setStatElements( elems );
-
-        return stats;
-    }
-
-    /**
-     * This is used by the event logging.
-     * <p>
-     * @return the location of the disk, either path or ip.
-     */
-    @Override
-    protected String getDiskLocation()
-    {
-        return dataFile.getFilePath();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCacheAttributes.java
deleted file mode 100644
index e520dda..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCacheAttributes.java
+++ /dev/null
@@ -1,118 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.block;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.disk.AbstractDiskCacheAttributes;
-
-/**
- * This holds attributes for Block Disk Cache configuration.
- * <p>
- * @author Aaron Smuts
- */
-public class BlockDiskCacheAttributes
-    extends AbstractDiskCacheAttributes
-{
-    /** Don't change */
-    private static final long serialVersionUID = 6568840097657265989L;
-
-    /** The size per block in bytes. */
-    private int blockSizeBytes;
-
-    /** Maximum number of keys to be kept in memory */
-    private static final int DEFAULT_MAX_KEY_SIZE = 5000;
-
-    /** -1 means no limit. */
-    private int maxKeySize = DEFAULT_MAX_KEY_SIZE;
-
-    /** How often should we persist the keys. */
-    private static final long DEFAULT_KEY_PERSISTENCE_INTERVAL_SECONDS = 5 * 60;
-
-    /** The keys will be persisted at this interval.  -1 mean never. */
-    private long keyPersistenceIntervalSeconds = DEFAULT_KEY_PERSISTENCE_INTERVAL_SECONDS;
-
-    /**
-     * The size of the blocks. All blocks are the same size.
-     * <p>
-     * @param blockSizeBytes The blockSizeBytes to set.
-     */
-    public void setBlockSizeBytes( int blockSizeBytes )
-    {
-        this.blockSizeBytes = blockSizeBytes;
-    }
-
-    /**
-     * @return Returns the blockSizeBytes.
-     */
-    public int getBlockSizeBytes()
-    {
-        return blockSizeBytes;
-    }
-
-    /**
-     * @param maxKeySize The maxKeySize to set.
-     */
-    public void setMaxKeySize( int maxKeySize )
-    {
-        this.maxKeySize = maxKeySize;
-    }
-
-    /**
-     * @return Returns the maxKeySize.
-     */
-    public int getMaxKeySize()
-    {
-        return maxKeySize;
-    }
-
-    /**
-     * @param keyPersistenceIntervalSeconds The keyPersistenceIntervalSeconds to set.
-     */
-    public void setKeyPersistenceIntervalSeconds( long keyPersistenceIntervalSeconds )
-    {
-        this.keyPersistenceIntervalSeconds = keyPersistenceIntervalSeconds;
-    }
-
-    /**
-     * @return Returns the keyPersistenceIntervalSeconds.
-     */
-    public long getKeyPersistenceIntervalSeconds()
-    {
-        return keyPersistenceIntervalSeconds;
-    }
-
-    /**
-     * Write out the values for debugging purposes.
-     * <p>
-     * @return String
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder str = new StringBuilder();
-        str.append( "\nBlockDiskAttributes " );
-        str.append( "\n DiskPath [" + this.getDiskPath() + "]" );
-        str.append( "\n MaxKeySize [" + this.getMaxKeySize() + "]" );
-        str.append( "\n MaxPurgatorySize [" + this.getMaxPurgatorySize() + "]" );
-        str.append( "\n BlockSizeBytes [" + this.getBlockSizeBytes() + "]" );
-        str.append( "\n KeyPersistenceIntervalSeconds [" + this.getKeyPersistenceIntervalSeconds() + "]" );
-        str.append( "\n DiskLimitType [" + this.getDiskLimitType() + "]" );
-        return str.toString();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCacheFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCacheFactory.java
deleted file mode 100644
index 7a88db4..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCacheFactory.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.block;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.AbstractAuxiliaryCacheFactory;
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * Creates disk cache instances.
- */
-public class BlockDiskCacheFactory
-    extends AbstractAuxiliaryCacheFactory
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog( BlockDiskCacheFactory.class );
-
-    /**
-     * Create an instance of the BlockDiskCache.
-     * <p>
-     * @param iaca the cache attributes for this cache
-     * @param cacheMgr This allows auxiliaries to reference the manager without assuming that it is
-     *            a singleton. This will allow JCS to be a non-singleton. Also, it makes it easier
-     *            to test.
-     * @param cacheEventLogger
-     * @param elementSerializer
-     * @return BlockDiskCache
-     */
-    @Override
-    public <K, V> BlockDiskCache<K, V> createCache( AuxiliaryCacheAttributes iaca, ICompositeCacheManager cacheMgr,
-                                       ICacheEventLogger cacheEventLogger, IElementSerializer elementSerializer )
-    {
-        BlockDiskCacheAttributes idca = (BlockDiskCacheAttributes) iaca;
-        log.debug("Creating DiskCache for attributes = {0}", idca);
-
-        BlockDiskCache<K, V> cache = new BlockDiskCache<>( idca, elementSerializer );
-        cache.setCacheEventLogger( cacheEventLogger );
-
-        return cache;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskElementDescriptor.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskElementDescriptor.java
deleted file mode 100644
index 73fd657..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskElementDescriptor.java
+++ /dev/null
@@ -1,132 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.block;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.Externalizable;
-import java.io.IOException;
-import java.io.ObjectInput;
-import java.io.ObjectOutput;
-import java.io.Serializable;
-
-/**
- * This represents an element on disk. This is used when we persist the keys. We only store the
- * block addresses in memory. We don't need the length here, since all the blocks are the same size
- * receyle bin.
- * <p>
- * @author Aaron Smuts
- */
-public class BlockDiskElementDescriptor<K>
-    implements Serializable, Externalizable
-{
-    /** Don't change */
-    private static final long serialVersionUID = -1400659301208101411L;
-
-    /** The key */
-    private K key;
-
-    /** The array of block numbers */
-    private int[] blocks;
-
-    /**
-     * @param key The key to set.
-     */
-    public void setKey( K key )
-    {
-        this.key = key;
-    }
-
-    /**
-     * @return Returns the key.
-     */
-    public K getKey()
-    {
-        return key;
-    }
-
-    /**
-     * @param blocks The blocks to set.
-     */
-    public void setBlocks( int[] blocks )
-    {
-        this.blocks = blocks;
-    }
-
-    /**
-     * This holds the block numbers. An item my be dispersed between multiple blocks.
-     * <p>
-     * @return Returns the blocks.
-     */
-    public int[] getBlocks()
-    {
-        return blocks;
-    }
-
-    /**
-     * For debugging.
-     * <p>
-     * @return Info on the descriptor.
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder buf = new StringBuilder();
-        buf.append( "\nBlockDiskElementDescriptor" );
-        buf.append( "\n key [" + this.getKey() + "]" );
-        buf.append( "\n blocks [" );
-        if ( this.getBlocks() != null )
-        {
-            for ( int i = 0; i < blocks.length; i++ )
-            {
-                buf.append( this.getBlocks()[i] );
-            }
-        }
-        buf.append( "]" );
-        return buf.toString();
-    }
-
-    /**
-     * Saves on reflection.
-     * <p>
-     * (non-Javadoc)
-     * @see java.io.Externalizable#readExternal(java.io.ObjectInput)
-     */
-    @Override
-    @SuppressWarnings("unchecked") // Need cast to K
-    public void readExternal( ObjectInput input )
-        throws IOException, ClassNotFoundException
-    {
-        this.key = (K) input.readObject();
-        this.blocks = (int[]) input.readObject();
-    }
-
-    /**
-     * Saves on reflection.
-     * <p>
-     * (non-Javadoc)
-     * @see java.io.Externalizable#writeExternal(java.io.ObjectOutput)
-     */
-    @Override
-    public void writeExternal( ObjectOutput output )
-        throws IOException
-    {
-        output.writeObject( this.key );
-        output.writeObject( this.blocks );
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskKeyStore.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskKeyStore.java
deleted file mode 100644
index 94118c4..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskKeyStore.java
+++ /dev/null
@@ -1,550 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.block;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.EOFException;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.apache.commons.jcs.auxiliary.disk.behavior.IDiskCacheAttributes.DiskLimitType;
-import org.apache.commons.jcs.io.ObjectInputStreamClassLoaderAware;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-import org.apache.commons.jcs.utils.struct.AbstractLRUMap;
-import org.apache.commons.jcs.utils.struct.LRUMap;
-import org.apache.commons.jcs.utils.timing.ElapsedTimer;
-
-/**
- * This is responsible for storing the keys.
- * <p>
- *
- * @author Aaron Smuts
- */
-public class BlockDiskKeyStore<K>
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog(BlockDiskKeyStore.class);
-
-    /** Attributes governing the behavior of the block disk cache. */
-    private final BlockDiskCacheAttributes blockDiskCacheAttributes;
-
-    /** The key to block map */
-    private Map<K, int[]> keyHash;
-
-    /** The file where we persist the keys */
-    private final File keyFile;
-
-    /** The name to prefix log messages with. */
-    protected final String logCacheName;
-
-    /** Name of the file where we persist the keys */
-    private final String fileName;
-
-    /** The maximum number of keys to store in memory */
-    private final int maxKeySize;
-
-    /**
-     * we need this so we can communicate free blocks to the data store when
-     * keys fall off the LRU
-     */
-    protected final BlockDiskCache<K, ?> blockDiskCache;
-
-    private DiskLimitType diskLimitType = DiskLimitType.COUNT;
-
-    private int blockSize;
-
-    /**
-     * Set the configuration options.
-     * <p>
-     *
-     * @param cacheAttributes
-     * @param blockDiskCache
-     *            used for freeing
-     */
-    public BlockDiskKeyStore(BlockDiskCacheAttributes cacheAttributes, BlockDiskCache<K, ?> blockDiskCache)
-    {
-        this.blockDiskCacheAttributes = cacheAttributes;
-        this.logCacheName = "Region [" + this.blockDiskCacheAttributes.getCacheName() + "] ";
-        this.fileName = this.blockDiskCacheAttributes.getCacheName();
-        this.maxKeySize = cacheAttributes.getMaxKeySize();
-        this.blockDiskCache = blockDiskCache;
-        this.diskLimitType = cacheAttributes.getDiskLimitType();
-        this.blockSize = cacheAttributes.getBlockSizeBytes();
-
-        File rootDirectory = cacheAttributes.getDiskPath();
-
-        log.info("{0}: Cache file root directory [{1}]", logCacheName, rootDirectory);
-
-        this.keyFile = new File(rootDirectory, fileName + ".key");
-
-        log.info("{0}: Key File [{1}]", logCacheName, this.keyFile.getAbsolutePath());
-
-        if (keyFile.length() > 0)
-        {
-            loadKeys();
-            if (!verify())
-            {
-                log.warn("{0}: Key File is invalid. Resetting file.", logCacheName);
-                initKeyMap();
-                reset();
-            }
-        }
-        else
-        {
-            initKeyMap();
-        }
-    }
-
-    /**
-     * Saves key file to disk. This gets the LRUMap entry set and write the
-     * entries out one by one after putting them in a wrapper.
-     */
-    protected void saveKeys()
-    {
-        try
-        {
-            ElapsedTimer timer = new ElapsedTimer();
-            int numKeys = keyHash.size();
-            log.info("{0}: Saving keys to [{1}], key count [{2}]", () -> logCacheName,
-                    () -> this.keyFile.getAbsolutePath(), () -> numKeys);
-
-            synchronized (keyFile)
-            {
-                FileOutputStream fos = new FileOutputStream(keyFile);
-                BufferedOutputStream bos = new BufferedOutputStream(fos, 65536);
-
-                try (ObjectOutputStream oos = new ObjectOutputStream(bos))
-                {
-                    if (!verify())
-                    {
-                        throw new IOException("Inconsistent key file");
-                    }
-                    // don't need to synchronize, since the underlying
-                    // collection makes a copy
-                    for (Map.Entry<K, int[]> entry : keyHash.entrySet())
-                    {
-                        BlockDiskElementDescriptor<K> descriptor = new BlockDiskElementDescriptor<>();
-                        descriptor.setKey(entry.getKey());
-                        descriptor.setBlocks(entry.getValue());
-                        // stream these out in the loop.
-                        oos.writeUnshared(descriptor);
-                    }
-                }
-            }
-
-            log.info("{0}: Finished saving keys. It took {1} to store {2} keys. Key file length [{3}]",
-                    () -> logCacheName, () -> timer.getElapsedTimeString(), () -> numKeys,
-                    () -> keyFile.length());
-        }
-        catch (IOException e)
-        {
-            log.error("{0}: Problem storing keys.", logCacheName, e);
-        }
-    }
-
-    /**
-     * Resets the file and creates a new key map.
-     */
-    protected void reset()
-    {
-        synchronized (keyFile)
-        {
-            clearMemoryMap();
-            saveKeys();
-        }
-    }
-
-    /**
-     * This is mainly used for testing. It leave the disk in tact, and just
-     * clears memory.
-     */
-    protected void clearMemoryMap()
-    {
-        this.keyHash.clear();
-    }
-
-    /**
-     * Create the map for keys that contain the index position on disk.
-     */
-    private void initKeyMap()
-    {
-        keyHash = null;
-        if (maxKeySize >= 0)
-        {
-            if (this.diskLimitType == DiskLimitType.SIZE)
-            {
-                keyHash = new LRUMapSizeLimited(maxKeySize);
-            }
-            else
-            {
-                keyHash = new LRUMapCountLimited(maxKeySize);
-            }
-            log.info("{0}: Set maxKeySize to: \"{1}\"", logCacheName, maxKeySize);
-        }
-        else
-        {
-            // If no max size, use a plain map for memory and processing
-            // efficiency.
-            keyHash = new HashMap<>();
-            // keyHash = Collections.synchronizedMap( new HashMap() );
-            log.info("{0}: Set maxKeySize to unlimited", logCacheName);
-        }
-    }
-
-    /**
-     * Loads the keys from the .key file. The keys are stored individually on
-     * disk. They are added one by one to an LRUMap..
-     */
-    protected void loadKeys()
-    {
-        log.info("{0}: Loading keys for {1}", () -> logCacheName, () -> keyFile.toString());
-
-        try
-        {
-            // create a key map to use.
-            initKeyMap();
-
-            HashMap<K, int[]> keys = new HashMap<>();
-
-            synchronized (keyFile)
-            {
-                FileInputStream fis = new FileInputStream(keyFile);
-                BufferedInputStream bis = new BufferedInputStream(fis, 65536);
-
-                try (ObjectInputStream ois = new ObjectInputStreamClassLoaderAware(bis, null))
-                {
-                    while (true)
-                    {
-                        @SuppressWarnings("unchecked")
-                        // Need to cast from Object
-                        BlockDiskElementDescriptor<K> descriptor = (BlockDiskElementDescriptor<K>) ois.readObject();
-                        if (descriptor != null)
-                        {
-                            keys.put(descriptor.getKey(), descriptor.getBlocks());
-                        }
-                    }
-                }
-                catch (EOFException eof)
-                {
-                    // nothing
-                }
-            }
-
-            if (!keys.isEmpty())
-            {
-                keyHash.putAll(keys);
-
-                log.debug("{0}: Found {1} in keys file.", logCacheName, keys.size());
-                log.info("{0}: Loaded keys from [{1}], key count: {2}; up to {3} will be available.",
-                        () -> logCacheName, () -> fileName, () -> keyHash.size(),
-                        () -> maxKeySize);
-            }
-        }
-        catch (Exception e)
-        {
-            log.error("{0}: Problem loading keys for file {1}", logCacheName, fileName, e);
-        }
-    }
-
-    /**
-     * Gets the entry set.
-     * <p>
-     *
-     * @return entry set.
-     */
-    public Set<Map.Entry<K, int[]>> entrySet()
-    {
-        return this.keyHash.entrySet();
-    }
-
-    /**
-     * Gets the key set.
-     * <p>
-     *
-     * @return key set.
-     */
-    public Set<K> keySet()
-    {
-        return this.keyHash.keySet();
-    }
-
-    /**
-     * Gets the size of the key hash.
-     * <p>
-     *
-     * @return the number of keys.
-     */
-    public int size()
-    {
-        return this.keyHash.size();
-    }
-
-    /**
-     * gets the object for the key.
-     * <p>
-     *
-     * @param key
-     * @return Object
-     */
-    public int[] get(K key)
-    {
-        return this.keyHash.get(key);
-    }
-
-    /**
-     * Puts a int[] in the keyStore.
-     * <p>
-     *
-     * @param key
-     * @param value
-     */
-    public void put(K key, int[] value)
-    {
-        this.keyHash.put(key, value);
-    }
-
-    /**
-     * Remove by key.
-     * <p>
-     *
-     * @param key
-     * @return BlockDiskElementDescriptor if it was present, else null
-     */
-    public int[] remove(K key)
-    {
-        return this.keyHash.remove(key);
-    }
-
-    /**
-     * Verify key store integrity
-     *
-     * @return true if key store is valid
-     */
-    private boolean verify()
-    {
-        Map<Integer, Set<K>> blockAllocationMap = new TreeMap<>();
-        for (Entry<K, int[]> e : keyHash.entrySet())
-        {
-            for (int block : e.getValue())
-            {
-                Set<K> keys = blockAllocationMap.get(block);
-                if (keys == null)
-                {
-                    keys = new HashSet<>();
-                    blockAllocationMap.put(block, keys);
-                }
-                else if (!log.isTraceEnabled())
-                {
-                    // keys are not null, and no debug - fail fast
-                    return false;
-                }
-                keys.add(e.getKey());
-            }
-        }
-        boolean ok = true;
-        if (log.isTraceEnabled())
-        {
-            for (Entry<Integer, Set<K>> e : blockAllocationMap.entrySet())
-            {
-                log.trace("Block {0}: {1}", e.getKey(), e.getValue());
-                if (e.getValue().size() > 1)
-                {
-                    ok = false;
-                }
-            }
-            return ok;
-        }
-        else
-        {
-            return ok;
-        }
-    }
-
-    /**
-     * Class for recycling and lru. This implements the LRU size overflow
-     * callback, so we can mark the blocks as free.
-     */
-    public class LRUMapSizeLimited extends AbstractLRUMap<K, int[]>
-    {
-        /**
-         * <code>tag</code> tells us which map we are working on.
-         */
-        public final static String TAG = "orig-lru-size";
-
-        // size of the content in kB
-        private AtomicInteger contentSize;
-        private int maxSize;
-
-        /**
-         * Default
-         */
-        public LRUMapSizeLimited()
-        {
-            this(-1);
-        }
-
-        /**
-         * @param maxSize
-         *            maximum cache size in kB
-         */
-        public LRUMapSizeLimited(int maxSize)
-        {
-            super();
-            this.maxSize = maxSize;
-            this.contentSize = new AtomicInteger(0);
-        }
-
-        // keep the content size in kB, so 2^31 kB is reasonable value
-        private void subLengthFromCacheSize(int[] value)
-        {
-            contentSize.addAndGet(value.length * blockSize / -1024 - 1);
-        }
-
-        // keep the content size in kB, so 2^31 kB is reasonable value
-        private void addLengthToCacheSize(int[] value)
-        {
-            contentSize.addAndGet(value.length * blockSize / 1024 + 1);
-        }
-
-        @Override
-        public int[] put(K key, int[] value)
-        {
-            int[] oldValue = null;
-
-            try
-            {
-                oldValue = super.put(key, value);
-            }
-            finally
-            {
-                if (value != null)
-                {
-                    addLengthToCacheSize(value);
-                }
-                if (oldValue != null)
-                {
-                    subLengthFromCacheSize(oldValue);
-                }
-            }
-
-            return oldValue;
-        }
-
-        @Override
-        public int[] remove(Object key)
-        {
-            int[] value = null;
-
-            try
-            {
-                value = super.remove(key);
-                return value;
-            }
-            finally
-            {
-                if (value != null)
-                {
-                    subLengthFromCacheSize(value);
-                }
-            }
-        }
-
-        /**
-         * This is called when the may key size is reached. The least recently
-         * used item will be passed here. We will store the position and size of
-         * the spot on disk in the recycle bin.
-         * <p>
-         *
-         * @param key
-         * @param value
-         */
-        @Override
-        protected void processRemovedLRU(K key, int[] value)
-        {
-            blockDiskCache.freeBlocks(value);
-            if (log.isDebugEnabled())
-            {
-                log.debug("{0}: Removing key: [{1}] from key store.", logCacheName, key);
-                log.debug("{0}: Key store size: [{1}].", logCacheName, super.size());
-            }
-
-            if (value != null)
-            {
-                subLengthFromCacheSize(value);
-            }
-        }
-
-        @Override
-        protected boolean shouldRemove()
-        {
-            return maxSize > 0 && contentSize.get() > maxSize && this.size() > 1;
-        }
-    }
-
-    /**
-     * Class for recycling and lru. This implements the LRU overflow callback,
-     * so we can mark the blocks as free.
-     */
-    public class LRUMapCountLimited extends LRUMap<K, int[]>
-    {
-        /**
-         * <code>tag</code> tells us which map we are working on.
-         */
-        public final static String TAG = "orig-lru-count";
-
-        public LRUMapCountLimited(int maxKeySize)
-        {
-            super(maxKeySize);
-        }
-
-        /**
-         * This is called when the may key size is reached. The least recently
-         * used item will be passed here. We will store the position and size of
-         * the spot on disk in the recycle bin.
-         * <p>
-         *
-         * @param key
-         * @param value
-         */
-        @Override
-        protected void processRemovedLRU(K key, int[] value)
-        {
-            blockDiskCache.freeBlocks(value);
-            if (log.isDebugEnabled())
-            {
-                log.debug("{0}: Removing key: [{1}] from key store.", logCacheName, key);
-                log.debug("{0}: Key store size: [{1}].", logCacheName, super.size());
-            }
-        }
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDisk.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDisk.java
deleted file mode 100644
index 7df2a58..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDisk.java
+++ /dev/null
@@ -1,279 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.indexed;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.nio.file.StandardOpenOption;
-
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/** Provides thread safe access to the underlying random access file. */
-public class IndexedDisk implements AutoCloseable
-{
-    /** The size of the header that indicates the amount of data stored in an occupied block. */
-    public static final byte HEADER_SIZE_BYTES = 4;
-
-    /** The serializer. */
-    private final IElementSerializer elementSerializer;
-
-    /** The logger */
-    private static final Log log = LogManager.getLog(IndexedDisk.class);
-
-    /** The path to the log directory. */
-    private final String filepath;
-
-    /** The data file. */
-    private final FileChannel fc;
-
-    /**
-     * Constructor for the Disk object
-     * <p>
-     * @param file
-     * @param elementSerializer
-     * @throws IOException
-     */
-    public IndexedDisk(File file, IElementSerializer elementSerializer)
-        throws IOException
-    {
-        this.filepath = file.getAbsolutePath();
-        this.elementSerializer = elementSerializer;
-        this.fc = FileChannel.open(file.toPath(),
-                StandardOpenOption.CREATE,
-                StandardOpenOption.READ,
-                StandardOpenOption.WRITE);
-    }
-
-    /**
-     * This reads an object from the given starting position on the file.
-     * <p>
-     * The first four bytes of the record should tell us how long it is. The data is read into a byte
-     * array and then an object is constructed from the byte array.
-     * <p>
-     * @return Serializable
-     * @param ded
-     * @throws IOException
-     * @throws ClassNotFoundException
-     */
-    protected <T> T readObject(IndexedDiskElementDescriptor ded)
-        throws IOException, ClassNotFoundException
-    {
-        String message = null;
-        boolean corrupted = false;
-        long fileLength = fc.size();
-        if (ded.pos > fileLength)
-        {
-            corrupted = true;
-            message = "Record " + ded + " starts past EOF.";
-        }
-        else
-        {
-            ByteBuffer datalength = ByteBuffer.allocate(HEADER_SIZE_BYTES);
-            fc.read(datalength, ded.pos);
-            datalength.flip();
-            int datalen = datalength.getInt();
-            if (ded.len != datalen)
-            {
-                corrupted = true;
-                message = "Record " + ded + " does not match data length on disk (" + datalen + ")";
-            }
-            else if (ded.pos + ded.len > fileLength)
-            {
-                corrupted = true;
-                message = "Record " + ded + " exceeds file length.";
-            }
-        }
-
-        if (corrupted)
-        {
-            log.warn("\n The file is corrupt: \n {0}", message);
-            throw new IOException("The File Is Corrupt, need to reset");
-        }
-
-        ByteBuffer data = ByteBuffer.allocate(ded.len);
-        fc.read(data, ded.pos + HEADER_SIZE_BYTES);
-        data.flip();
-
-        return elementSerializer.deSerialize(data.array(), null);
-    }
-
-    /**
-     * Moves the data stored from one position to another. The descriptor's position is updated.
-     * <p>
-     * @param ded
-     * @param newPosition
-     * @throws IOException
-     */
-    protected void move(final IndexedDiskElementDescriptor ded, final long newPosition)
-        throws IOException
-    {
-        ByteBuffer datalength = ByteBuffer.allocate(HEADER_SIZE_BYTES);
-        fc.read(datalength, ded.pos);
-        datalength.flip();
-        int length = datalength.getInt();
-
-        if (length != ded.len)
-        {
-            throw new IOException("Mismatched memory and disk length (" + length + ") for " + ded);
-        }
-
-        // TODO: more checks?
-
-        long readPos = ded.pos;
-        long writePos = newPosition;
-
-        // header len + data len
-        int remaining = HEADER_SIZE_BYTES + length;
-        ByteBuffer buffer = ByteBuffer.allocate(16384);
-
-        while (remaining > 0)
-        {
-            // chunk it
-            int chunkSize = Math.min(remaining, buffer.capacity());
-            buffer.limit(chunkSize);
-            fc.read(buffer, readPos);
-            buffer.flip();
-            fc.write(buffer, writePos);
-            buffer.clear();
-
-            writePos += chunkSize;
-            readPos += chunkSize;
-            remaining -= chunkSize;
-        }
-
-        ded.pos = newPosition;
-    }
-
-    /**
-     * Writes the given byte array to the Disk at the specified position.
-     * <p>
-     * @param data
-     * @param ded
-     * @return true if we wrote successfully
-     * @throws IOException
-     */
-    protected boolean write(IndexedDiskElementDescriptor ded, byte[] data)
-        throws IOException
-    {
-        long pos = ded.pos;
-        if (log.isTraceEnabled())
-        {
-            log.trace("write> pos={0}", pos);
-            log.trace("{0} -- data.length = {1}", fc, data.length);
-        }
-
-        if (data.length != ded.len)
-        {
-            throw new IOException("Mismatched descriptor and data lengths");
-        }
-
-        ByteBuffer headerBuffer = ByteBuffer.allocate(HEADER_SIZE_BYTES);
-        headerBuffer.putInt(data.length);
-        // write the header
-        headerBuffer.flip();
-        int written = fc.write(headerBuffer, pos);
-        assert written == HEADER_SIZE_BYTES;
-
-        //write the data
-        ByteBuffer dataBuffer = ByteBuffer.wrap(data);
-        written = fc.write(dataBuffer, pos + HEADER_SIZE_BYTES);
-
-        return written == data.length;
-    }
-
-    /**
-     * Serializes the object and write it out to the given position.
-     * <p>
-     * TODO: make this take a ded as well.
-     * @param obj
-     * @param pos
-     * @throws IOException
-     */
-    protected <T> void writeObject(T obj, long pos)
-        throws IOException
-    {
-        byte[] data = elementSerializer.serialize(obj);
-        write(new IndexedDiskElementDescriptor(pos, data.length), data);
-    }
-
-    /**
-     * Returns the raf length.
-     * <p>
-     * @return the length of the file.
-     * @throws IOException
-     */
-    protected long length()
-        throws IOException
-    {
-        return fc.size();
-    }
-
-    /**
-     * Closes the raf.
-     * <p>
-     * @throws IOException
-     */
-    @Override
-    public void close()
-        throws IOException
-    {
-        fc.close();
-    }
-
-    /**
-     * Sets the raf to empty.
-     * <p>
-     * @throws IOException
-     */
-    protected synchronized void reset()
-        throws IOException
-    {
-        log.debug("Resetting Indexed File [{0}]", filepath);
-        fc.truncate(0);
-        fc.force(true);
-    }
-
-    /**
-     * Truncates the file to a given length.
-     * <p>
-     * @param length the new length of the file
-     * @throws IOException
-     */
-    protected void truncate(long length)
-        throws IOException
-    {
-        log.info("Truncating file [{0}] to {1}", filepath, length);
-        fc.truncate(length);
-    }
-
-    /**
-     * This is used for debugging.
-     * <p>
-     * @return the file path.
-     */
-    protected String getFilePath()
-    {
-        return filepath;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCache.java
deleted file mode 100644
index 68d9eef..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCache.java
+++ /dev/null
@@ -1,1680 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.indexed;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.File;
-import java.io.IOException;
-import java.io.Serializable;
-import java.nio.file.Files;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentSkipListSet;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes;
-import org.apache.commons.jcs.auxiliary.disk.AbstractDiskCache;
-import org.apache.commons.jcs.auxiliary.disk.behavior.IDiskCacheAttributes.DiskLimitType;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.engine.control.group.GroupAttrName;
-import org.apache.commons.jcs.engine.control.group.GroupId;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEvent;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-import org.apache.commons.jcs.engine.stats.StatElement;
-import org.apache.commons.jcs.engine.stats.Stats;
-import org.apache.commons.jcs.engine.stats.behavior.IStatElement;
-import org.apache.commons.jcs.engine.stats.behavior.IStats;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-import org.apache.commons.jcs.utils.struct.AbstractLRUMap;
-import org.apache.commons.jcs.utils.struct.LRUMap;
-import org.apache.commons.jcs.utils.timing.ElapsedTimer;
-
-/**
- * Disk cache that uses a RandomAccessFile with keys stored in memory. The maximum number of keys
- * stored in memory is configurable. The disk cache tries to recycle spots on disk to limit file
- * expansion.
- */
-public class IndexedDiskCache<K, V> extends AbstractDiskCache<K, V>
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog(IndexedDiskCache.class);
-
-    /** Cache name used in log messages */
-    protected final String logCacheName;
-
-    /** The name of the file where the data is stored */
-    private final String fileName;
-
-    /** The IndexedDisk manages reads and writes to the data file. */
-    private IndexedDisk dataFile;
-
-    /** The IndexedDisk manages reads and writes to the key file. */
-    private IndexedDisk keyFile;
-
-    /** Map containing the keys and disk offsets. */
-    private final Map<K, IndexedDiskElementDescriptor> keyHash;
-
-    /** The maximum number of keys that we will keep in memory. */
-    private final int maxKeySize;
-
-    /** A handle on the data file. */
-    private File rafDir;
-
-    /** Should we keep adding to the recycle bin. False during optimization. */
-    private boolean doRecycle = true;
-
-    /** Should we optimize real time */
-    private boolean isRealTimeOptimizationEnabled = true;
-
-    /** Should we optimize on shutdown. */
-    private boolean isShutdownOptimizationEnabled = true;
-
-    /** are we currently optimizing the files */
-    private boolean isOptimizing = false;
-
-    /** The number of times the file has been optimized. */
-    private int timesOptimized = 0;
-
-    /** The thread optimizing the file. */
-    private volatile Thread currentOptimizationThread;
-
-    /** used for counting the number of requests */
-    private int removeCount = 0;
-
-    /** Should we queue puts. True when optimizing. We write the queue post optimization. */
-    private boolean queueInput = false;
-
-    /** list where puts made during optimization are made */
-    private final ConcurrentSkipListSet<IndexedDiskElementDescriptor> queuedPutList;
-
-    /** RECYLCE BIN -- array of empty spots */
-    private final ConcurrentSkipListSet<IndexedDiskElementDescriptor> recycle;
-
-    /** User configurable parameters */
-    private final IndexedDiskCacheAttributes cattr;
-
-    /** How many slots have we recycled. */
-    private int recycleCnt = 0;
-
-    /** How many items were there on startup. */
-    private int startupSize = 0;
-
-    /** the number of bytes free on disk. */
-    private AtomicLong bytesFree = new AtomicLong(0);
-
-    /** mode we are working on (size or count limited **/
-    private DiskLimitType diskLimitType = DiskLimitType.COUNT;
-
-    /** simple stat */
-    private AtomicInteger hitCount = new AtomicInteger(0);
-
-    /**
-     * Use this lock to synchronize reads and writes to the underlying storage mechanism.
-     */
-    protected ReentrantReadWriteLock storageLock = new ReentrantReadWriteLock();
-
-    /**
-     * Constructor for the DiskCache object.
-     * <p>
-     *
-     * @param cacheAttributes
-     */
-    public IndexedDiskCache(IndexedDiskCacheAttributes cacheAttributes)
-    {
-        this(cacheAttributes, null);
-    }
-
-    /**
-     * Constructor for the DiskCache object.
-     * <p>
-     *
-     * @param cattr
-     * @param elementSerializer
-     *            used if supplied, the super's super will not set a null
-     */
-    public IndexedDiskCache(IndexedDiskCacheAttributes cattr, IElementSerializer elementSerializer)
-    {
-        super(cattr);
-
-        setElementSerializer(elementSerializer);
-
-        this.cattr = cattr;
-        this.maxKeySize = cattr.getMaxKeySize();
-        this.isRealTimeOptimizationEnabled = cattr.getOptimizeAtRemoveCount() > 0;
-        this.isShutdownOptimizationEnabled = cattr.isOptimizeOnShutdown();
-        this.logCacheName = "Region [" + getCacheName() + "] ";
-        this.diskLimitType = cattr.getDiskLimitType();
-        // Make a clean file name
-        this.fileName = getCacheName().replaceAll("[^a-zA-Z0-9-_\\.]", "_");
-        this.keyHash = createInitialKeyMap();
-        this.queuedPutList = new ConcurrentSkipListSet<>(new PositionComparator());
-        this.recycle = new ConcurrentSkipListSet<>();
-
-        try
-        {
-            initializeFileSystem(cattr);
-            initializeKeysAndData(cattr);
-
-            // Initialization finished successfully, so set alive to true.
-            setAlive(true);
-            log.info("{0}: Indexed Disk Cache is alive.", logCacheName);
-
-            // TODO: Should we improve detection of whether or not the file should be optimized.
-            if (isRealTimeOptimizationEnabled && keyHash.size() > 0)
-            {
-                // Kick off a real time optimization, in case we didn't do a final optimization.
-                doOptimizeRealTime();
-            }
-        }
-        catch (IOException e)
-        {
-            log.error("{0}: Failure initializing for fileName: {1} and directory: {2}",
-                    logCacheName, fileName, this.rafDir.getAbsolutePath(), e);
-        }
-    }
-
-    /**
-     * Tries to create the root directory if it does not already exist.
-     * <p>
-     *
-     * @param cattr
-     */
-    private void initializeFileSystem(IndexedDiskCacheAttributes cattr)
-    {
-        this.rafDir = cattr.getDiskPath();
-        log.info("{0}: Cache file root directory: {1}", logCacheName, rafDir);
-    }
-
-    /**
-     * Creates the key and data disk caches.
-     * <p>
-     * Loads any keys if they are present and ClearDiskOnStartup is false.
-     * <p>
-     *
-     * @param cattr
-     * @throws IOException
-     */
-    private void initializeKeysAndData(IndexedDiskCacheAttributes cattr) throws IOException
-    {
-        this.dataFile = new IndexedDisk(new File(rafDir, fileName + ".data"), getElementSerializer());
-        this.keyFile = new IndexedDisk(new File(rafDir, fileName + ".key"), getElementSerializer());
-
-        if (cattr.isClearDiskOnStartup())
-        {
-            log.info("{0}: ClearDiskOnStartup is set to true.  Ingnoring any persisted data.",
-                    logCacheName);
-            initializeEmptyStore();
-        }
-        else if (keyFile.length() > 0)
-        {
-            // If the key file has contents, try to initialize the keys
-            // from it. In no keys are loaded reset the data file.
-            initializeStoreFromPersistedData();
-        }
-        else
-        {
-            // Otherwise start with a new empty map for the keys, and reset
-            // the data file if it has contents.
-            initializeEmptyStore();
-        }
-    }
-
-    /**
-     * Initializes an empty disk cache.
-     * <p>
-     *
-     * @throws IOException
-     */
-    private void initializeEmptyStore() throws IOException
-    {
-        this.keyHash.clear();
-
-        if (dataFile.length() > 0)
-        {
-            dataFile.reset();
-        }
-    }
-
-    /**
-     * Loads any persisted data and checks for consistency. If there is a consistency issue, the
-     * files are cleared.
-     * <p>
-     *
-     * @throws IOException
-     */
-    private void initializeStoreFromPersistedData() throws IOException
-    {
-        loadKeys();
-
-        if (keyHash.isEmpty())
-        {
-            dataFile.reset();
-        }
-        else
-        {
-            boolean isOk = checkKeyDataConsistency(false);
-            if (!isOk)
-            {
-                keyHash.clear();
-                keyFile.reset();
-                dataFile.reset();
-                log.warn("{0}: Corruption detected. Resetting data and keys files.", logCacheName);
-            }
-            else
-            {
-                synchronized (this)
-                {
-                    startupSize = keyHash.size();
-                }
-            }
-        }
-    }
-
-    /**
-     * Loads the keys from the .key file. The keys are stored in a HashMap on disk. This is
-     * converted into a LRUMap.
-     */
-    protected void loadKeys()
-    {
-        log.debug("{0}: Loading keys for {1}", () -> logCacheName, () -> keyFile.toString());
-
-        storageLock.writeLock().lock();
-
-        try
-        {
-            // clear a key map to use.
-            keyHash.clear();
-
-            HashMap<K, IndexedDiskElementDescriptor> keys = keyFile.readObject(
-                new IndexedDiskElementDescriptor(0, (int) keyFile.length() - IndexedDisk.HEADER_SIZE_BYTES));
-
-            if (keys != null)
-            {
-                log.debug("{0}: Found {1} in keys file.", logCacheName, keys.size());
-
-                keyHash.putAll(keys);
-
-                log.info("{0}: Loaded keys from [{1}], key count: {2}; up to {3} will be available.",
-                        () -> logCacheName, () -> fileName, () -> keyHash.size(), () -> maxKeySize);
-            }
-
-            if (log.isTraceEnabled())
-            {
-                dump(false);
-            }
-        }
-        catch (Exception e)
-        {
-            log.error("{0}: Problem loading keys for file {1}", logCacheName, fileName, e);
-        }
-        finally
-        {
-            storageLock.writeLock().unlock();
-        }
-    }
-
-    /**
-     * Check for minimal consistency between the keys and the datafile. Makes sure no starting
-     * positions in the keys exceed the file length.
-     * <p>
-     * The caller should take the appropriate action if the keys and data are not consistent.
-     *
-     * @param checkForDedOverlaps
-     *            if <code>true</code>, do a more thorough check by checking for
-     *            data overlap
-     * @return <code>true</code> if the test passes
-     */
-    private boolean checkKeyDataConsistency(boolean checkForDedOverlaps)
-    {
-        ElapsedTimer timer = new ElapsedTimer();
-        log.debug("{0}: Performing inital consistency check", logCacheName);
-
-        boolean isOk = true;
-        long fileLength = 0;
-        try
-        {
-            fileLength = dataFile.length();
-
-            for (Map.Entry<K, IndexedDiskElementDescriptor> e : keyHash.entrySet())
-            {
-                IndexedDiskElementDescriptor ded = e.getValue();
-
-                isOk = ded.pos + IndexedDisk.HEADER_SIZE_BYTES + ded.len <= fileLength;
-
-                if (!isOk)
-                {
-                    log.warn("{0}: The dataFile is corrupted!\n raf.length() = {1}\n ded.pos = {2}",
-                            logCacheName, fileLength, ded.pos);
-                    break;
-                }
-            }
-
-            if (isOk && checkForDedOverlaps)
-            {
-                isOk = checkForDedOverlaps(createPositionSortedDescriptorList());
-            }
-        }
-        catch (IOException e)
-        {
-            log.error(e);
-            isOk = false;
-        }
-
-        log.info("{0}: Finished inital consistency check, isOk = {1} in {2}",
-                logCacheName, isOk, timer.getElapsedTimeString());
-
-        return isOk;
-    }
-
-    /**
-     * Detects any overlapping elements. This expects a sorted list.
-     * <p>
-     * The total length of an item is IndexedDisk.RECORD_HEADER + ded.len.
-     * <p>
-     *
-     * @param sortedDescriptors
-     * @return false if there are overlaps.
-     */
-    protected boolean checkForDedOverlaps(IndexedDiskElementDescriptor[] sortedDescriptors)
-    {
-        ElapsedTimer timer = new ElapsedTimer();
-        boolean isOk = true;
-        long expectedNextPos = 0;
-        for (int i = 0; i < sortedDescriptors.length; i++)
-        {
-            IndexedDiskElementDescriptor ded = sortedDescriptors[i];
-            if (expectedNextPos > ded.pos)
-            {
-                log.error("{0}: Corrupt file: overlapping deds {1}", logCacheName, ded);
-                isOk = false;
-                break;
-            }
-            else
-            {
-                expectedNextPos = ded.pos + IndexedDisk.HEADER_SIZE_BYTES + ded.len;
-            }
-        }
-        log.debug("{0}: Check for DED overlaps took {1} ms.", () -> logCacheName,
-                () -> timer.getElapsedTime());
-
-        return isOk;
-    }
-
-    /**
-     * Saves key file to disk. This converts the LRUMap to a HashMap for deserialization.
-     */
-    protected void saveKeys()
-    {
-        try
-        {
-            log.info("{0}: Saving keys to: {1}, key count: {2}",
-                    () -> logCacheName, () -> fileName, () -> keyHash.size());
-
-            keyFile.reset();
-
-            HashMap<K, IndexedDiskElementDescriptor> keys = new HashMap<>();
-            keys.putAll(keyHash);
-
-            if (keys.size() > 0)
-            {
-                keyFile.writeObject(keys, 0);
-            }
-
-            log.info("{0}: Finished saving keys.", logCacheName);
-        }
-        catch (IOException e)
-        {
-            log.error("{0}: Problem storing keys.", logCacheName, e);
-        }
-    }
-
-    /**
-     * Update the disk cache. Called from the Queue. Makes sure the Item has not been retrieved from
-     * purgatory while in queue for disk. Remove items from purgatory when they go to disk.
-     * <p>
-     *
-     * @param ce
-     *            The ICacheElement&lt;K, V&gt; to put to disk.
-     */
-    @Override
-    protected void processUpdate(ICacheElement<K, V> ce)
-    {
-        if (!isAlive())
-        {
-            log.error("{0}: No longer alive; aborting put of key = {1}",
-                    () -> logCacheName, () -> ce.getKey());
-            return;
-        }
-
-        log.debug("{0}: Storing element on disk, key: {1}",
-                () -> logCacheName, () -> ce.getKey());
-
-        IndexedDiskElementDescriptor ded = null;
-
-        // old element with same key
-        IndexedDiskElementDescriptor old = null;
-
-        try
-        {
-            byte[] data = getElementSerializer().serialize(ce);
-
-            // make sure this only locks for one particular cache region
-            storageLock.writeLock().lock();
-            try
-            {
-                old = keyHash.get(ce.getKey());
-
-                // Item with the same key already exists in file.
-                // Try to reuse the location if possible.
-                if (old != null && data.length <= old.len)
-                {
-                    // Reuse the old ded. The defrag relies on ded updates by reference, not
-                    // replacement.
-                    ded = old;
-                    ded.len = data.length;
-                }
-                else
-                {
-                    // we need this to compare in the recycle bin
-                    ded = new IndexedDiskElementDescriptor(dataFile.length(), data.length);
-
-                    if (doRecycle)
-                    {
-                        IndexedDiskElementDescriptor rep = recycle.ceiling(ded);
-                        if (rep != null)
-                        {
-                            // remove element from recycle bin
-                            recycle.remove(rep);
-                            ded = rep;
-                            ded.len = data.length;
-                            recycleCnt++;
-                            this.adjustBytesFree(ded, false);
-                            log.debug("{0}: using recycled ded {1} rep.len = {2} ded.len = {3}",
-                                    logCacheName, ded.pos, rep.len, ded.len);
-                        }
-                    }
-
-                    // Put it in the map
-                    keyHash.put(ce.getKey(), ded);
-
-                    if (queueInput)
-                    {
-                        queuedPutList.add(ded);
-                        log.debug("{0}: added to queued put list. {1}",
-                                () -> logCacheName, () -> queuedPutList.size());
-                    }
-
-                    // add the old slot to the recycle bin
-                    if (old != null)
-                    {
-                        addToRecycleBin(old);
-                    }
-                }
-
-                dataFile.write(ded, data);
-            }
-            finally
-            {
-                storageLock.writeLock().unlock();
-            }
-
-            log.debug("{0}: Put to file: {1}, key: {2}, position: {3}, size: {4}",
-                    logCacheName, fileName, ce.getKey(), ded.pos, ded.len);
-        }
-        catch (IOException e)
-        {
-            log.error("{0}: Failure updating element, key: {1} old: {2}",
-                    logCacheName, ce.getKey(), old, e);
-        }
-    }
-
-    /**
-     * Gets the key, then goes to disk to get the object.
-     * <p>
-     *
-     * @param key
-     * @return ICacheElement&lt;K, V&gt; or null
-     * @see AbstractDiskCache#doGet
-     */
-    @Override
-    protected ICacheElement<K, V> processGet(K key)
-    {
-        if (!isAlive())
-        {
-            log.error("{0}: No longer alive so returning null for key = {1}",
-                    logCacheName, key);
-            return null;
-        }
-
-        log.debug("{0}: Trying to get from disk: {1}", logCacheName, key);
-
-        ICacheElement<K, V> object = null;
-        try
-        {
-            storageLock.readLock().lock();
-            try
-            {
-                object = readElement(key);
-            }
-            finally
-            {
-                storageLock.readLock().unlock();
-            }
-
-            if (object != null)
-            {
-                hitCount.incrementAndGet();
-            }
-        }
-        catch (IOException ioe)
-        {
-            log.error("{0}: Failure getting from disk, key = {1}", logCacheName, key, ioe);
-            reset();
-        }
-        return object;
-    }
-
-    /**
-     * Gets matching items from the cache.
-     * <p>
-     *
-     * @param pattern
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache matching keys
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> processGetMatching(String pattern)
-    {
-        Map<K, ICacheElement<K, V>> elements = new HashMap<>();
-        Set<K> keyArray = null;
-        storageLock.readLock().lock();
-        try
-        {
-            keyArray = new HashSet<>(keyHash.keySet());
-        }
-        finally
-        {
-            storageLock.readLock().unlock();
-        }
-
-        Set<K> matchingKeys = getKeyMatcher().getMatchingKeysFromArray(pattern, keyArray);
-
-        for (K key : matchingKeys)
-        {
-            ICacheElement<K, V> element = processGet(key);
-            if (element != null)
-            {
-                elements.put(key, element);
-            }
-        }
-        return elements;
-    }
-
-    /**
-     * Reads the item from disk.
-     * <p>
-     *
-     * @param key
-     * @return ICacheElement
-     * @throws IOException
-     */
-    private ICacheElement<K, V> readElement(K key) throws IOException
-    {
-        ICacheElement<K, V> object = null;
-
-        IndexedDiskElementDescriptor ded = keyHash.get(key);
-
-        if (ded != null)
-        {
-            log.debug("{0}: Found on disk, key: ", logCacheName, key);
-
-            try
-            {
-                ICacheElement<K, V> readObject = dataFile.readObject(ded);
-                object = readObject;
-                // TODO consider checking key equality and throwing if there is a failure
-            }
-            catch (IOException e)
-            {
-                log.error("{0}: IO Exception, Problem reading object from file", logCacheName, e);
-                throw e;
-            }
-            catch (Exception e)
-            {
-                log.error("{0}: Exception, Problem reading object from file", logCacheName, e);
-                throw new IOException(logCacheName + "Problem reading object from disk.", e);
-            }
-        }
-
-        return object;
-    }
-
-    /**
-     * Return the keys in this cache.
-     * <p>
-     *
-     * @see org.apache.commons.jcs.auxiliary.disk.AbstractDiskCache#getKeySet()
-     */
-    @Override
-    public Set<K> getKeySet() throws IOException
-    {
-        HashSet<K> keys = new HashSet<>();
-
-        storageLock.readLock().lock();
-
-        try
-        {
-            keys.addAll(this.keyHash.keySet());
-        }
-        finally
-        {
-            storageLock.readLock().unlock();
-        }
-
-        return keys;
-    }
-
-    /**
-     * Returns true if the removal was successful; or false if there is nothing to remove. Current
-     * implementation always result in a disk orphan.
-     * <p>
-     *
-     * @return true if at least one item was removed.
-     * @param key
-     */
-    @Override
-    protected boolean processRemove(K key)
-    {
-        if (!isAlive())
-        {
-            log.error("{0}: No longer alive so returning false for key = {1}", logCacheName, key);
-            return false;
-        }
-
-        if (key == null)
-        {
-            return false;
-        }
-
-        boolean reset = false;
-        boolean removed = false;
-        try
-        {
-            storageLock.writeLock().lock();
-
-            if (key instanceof String && key.toString().endsWith(NAME_COMPONENT_DELIMITER))
-            {
-                removed = performPartialKeyRemoval((String) key);
-            }
-            else if (key instanceof GroupAttrName && ((GroupAttrName<?>) key).attrName == null)
-            {
-                removed = performGroupRemoval(((GroupAttrName<?>) key).groupId);
-            }
-            else
-            {
-                removed = performSingleKeyRemoval(key);
-            }
-        }
-        finally
-        {
-            storageLock.writeLock().unlock();
-        }
-
-        if (reset)
-        {
-            reset();
-        }
-
-        // this increments the remove count.
-        // there is no reason to call this if an item was not removed.
-        if (removed)
-        {
-            doOptimizeRealTime();
-        }
-
-        return removed;
-    }
-
-    /**
-     * Iterates over the keyset. Builds a list of matches. Removes all the keys in the list. Does
-     * not remove via the iterator, since the map impl may not support it.
-     * <p>
-     * This operates under a lock obtained in doRemove().
-     * <p>
-     *
-     * @param key
-     * @return true if there was a match
-     */
-    private boolean performPartialKeyRemoval(String key)
-    {
-        boolean removed = false;
-
-        // remove all keys of the same name hierarchy.
-        List<K> itemsToRemove = new LinkedList<>();
-
-        for (K k : keyHash.keySet())
-        {
-            if (k instanceof String && k.toString().startsWith(key))
-            {
-                itemsToRemove.add(k);
-            }
-        }
-
-        // remove matches.
-        for (K fullKey : itemsToRemove)
-        {
-            // Don't add to recycle bin here
-            // https://issues.apache.org/jira/browse/JCS-67
-            performSingleKeyRemoval(fullKey);
-            removed = true;
-            // TODO this needs to update the remove count separately
-        }
-
-        return removed;
-    }
-
-    /**
-     * Remove all elements from the group. This does not use the iterator to remove. It builds a
-     * list of group elements and then removes them one by one.
-     * <p>
-     * This operates under a lock obtained in doRemove().
-     * <p>
-     *
-     * @param key
-     * @return true if an element was removed
-     */
-    private boolean performGroupRemoval(GroupId key)
-    {
-        boolean removed = false;
-
-        // remove all keys of the same name group.
-        List<K> itemsToRemove = new LinkedList<>();
-
-        // remove all keys of the same name hierarchy.
-        for (K k : keyHash.keySet())
-        {
-            if (k instanceof GroupAttrName && ((GroupAttrName<?>) k).groupId.equals(key))
-            {
-                itemsToRemove.add(k);
-            }
-        }
-
-        // remove matches.
-        for (K fullKey : itemsToRemove)
-        {
-            // Don't add to recycle bin here
-            // https://issues.apache.org/jira/browse/JCS-67
-            performSingleKeyRemoval(fullKey);
-            removed = true;
-            // TODO this needs to update the remove count separately
-        }
-
-        return removed;
-    }
-
-    /**
-     * Removes an individual key from the cache.
-     * <p>
-     * This operates under a lock obtained in doRemove().
-     * <p>
-     *
-     * @param key
-     * @return true if an item was removed.
-     */
-    private boolean performSingleKeyRemoval(K key)
-    {
-        boolean removed;
-        // remove single item.
-        IndexedDiskElementDescriptor ded = keyHash.remove(key);
-        removed = ded != null;
-        addToRecycleBin(ded);
-
-        log.debug("{0}: Disk removal: Removed from key hash, key [{1}] removed = {2}",
-                logCacheName, key, removed);
-        return removed;
-    }
-
-    /**
-     * Remove all the items from the disk cache by reseting everything.
-     */
-    @Override
-    public void processRemoveAll()
-    {
-        ICacheEvent<String> cacheEvent =
-                createICacheEvent(getCacheName(), "all", ICacheEventLogger.REMOVEALL_EVENT);
-        try
-        {
-            reset();
-        }
-        finally
-        {
-            logICacheEvent(cacheEvent);
-        }
-    }
-
-    /**
-     * Reset effectively clears the disk cache, creating new files, recycle bins, and keymaps.
-     * <p>
-     * It can be used to handle errors by last resort, force content update, or removeall.
-     */
-    private void reset()
-    {
-        log.info("{0}: Resetting cache", logCacheName);
-
-        try
-        {
-            storageLock.writeLock().lock();
-
-            if (dataFile != null)
-            {
-                dataFile.close();
-            }
-
-            File dataFileTemp = new File(rafDir, fileName + ".data");
-            Files.delete(dataFileTemp.toPath());
-
-            if (keyFile != null)
-            {
-                keyFile.close();
-            }
-            File keyFileTemp = new File(rafDir, fileName + ".key");
-            Files.delete(keyFileTemp.toPath());
-
-            dataFile = new IndexedDisk(dataFileTemp, getElementSerializer());
-            keyFile = new IndexedDisk(keyFileTemp, getElementSerializer());
-
-            this.recycle.clear();
-            this.keyHash.clear();
-        }
-        catch (IOException e)
-        {
-            log.error("{0}: Failure resetting state", logCacheName, e);
-        }
-        finally
-        {
-            storageLock.writeLock().unlock();
-        }
-    }
-
-    /**
-     * Create the map for keys that contain the index position on disk.
-     *
-     * @return a new empty Map for keys and IndexedDiskElementDescriptors
-     */
-    private Map<K, IndexedDiskElementDescriptor> createInitialKeyMap()
-    {
-        Map<K, IndexedDiskElementDescriptor> keyMap = null;
-        if (maxKeySize >= 0)
-        {
-            if (this.diskLimitType == DiskLimitType.COUNT)
-            {
-                keyMap = new LRUMapCountLimited(maxKeySize);
-            }
-            else
-            {
-                keyMap = new LRUMapSizeLimited(maxKeySize);
-            }
-
-            log.info("{0}: Set maxKeySize to: \"{1}\"", logCacheName, maxKeySize);
-        }
-        else
-        {
-            // If no max size, use a plain map for memory and processing efficiency.
-            keyMap = new HashMap<>();
-            // keyHash = Collections.synchronizedMap( new HashMap() );
-            log.info("{0}: Set maxKeySize to unlimited", logCacheName);
-        }
-
-        return keyMap;
-    }
-
-    /**
-     * Dispose of the disk cache in a background thread. Joins against this thread to put a cap on
-     * the disposal time.
-     * <p>
-     * TODO make dispose window configurable.
-     */
-    @Override
-    public void processDispose()
-    {
-        ICacheEvent<String> cacheEvent = createICacheEvent(getCacheName(), "none", ICacheEventLogger.DISPOSE_EVENT);
-        try
-        {
-            Thread t = new Thread(this::disposeInternal, "IndexedDiskCache-DisposalThread");
-            t.start();
-            // wait up to 60 seconds for dispose and then quit if not done.
-            try
-            {
-                t.join(60 * 1000);
-            }
-            catch (InterruptedException ex)
-            {
-                log.error("{0}: Interrupted while waiting for disposal thread to finish.",
-                        logCacheName, ex);
-            }
-        }
-        finally
-        {
-            logICacheEvent(cacheEvent);
-        }
-    }
-
-    /**
-     * Internal method that handles the disposal.
-     */
-    protected void disposeInternal()
-    {
-        if (!isAlive())
-        {
-            log.error("{0}: Not alive and dispose was called, filename: {1}",
-                    logCacheName, fileName);
-            return;
-        }
-
-        // Prevents any interaction with the cache while we're shutting down.
-        setAlive(false);
-
-        Thread optimizationThread = currentOptimizationThread;
-        if (isRealTimeOptimizationEnabled && optimizationThread != null)
-        {
-            // Join with the current optimization thread.
-            log.debug("{0}: In dispose, optimization already in progress; waiting for completion.",
-                    logCacheName);
-
-            try
-            {
-                optimizationThread.join();
-            }
-            catch (InterruptedException e)
-            {
-                log.error("{0}: Unable to join current optimization thread.",
-                        logCacheName, e);
-            }
-        }
-        else if (isShutdownOptimizationEnabled && this.getBytesFree() > 0)
-        {
-            optimizeFile();
-        }
-
-        saveKeys();
-
-        try
-        {
-            log.debug("{0}: Closing files, base filename: {1}", logCacheName,
-                    fileName);
-            dataFile.close();
-            dataFile = null;
-            keyFile.close();
-            keyFile = null;
-        }
-        catch (IOException e)
-        {
-            log.error("{0}: Failure closing files in dispose, filename: {1}",
-                    logCacheName, fileName, e);
-        }
-
-        log.info("{0}: Shutdown complete.", logCacheName);
-    }
-
-    /**
-     * Add descriptor to recycle bin if it is not null. Adds the length of the item to the bytes
-     * free.
-     * <p>
-     * This is called in three places: (1) When an item is removed. All item removals funnel down to the removeSingleItem method.
-     * (2) When an item on disk is updated with a value that will not fit in the previous slot. (3) When the max key size is
-     * reached, the freed slot will be added.
-     * <p>
-     *
-     * @param ded
-     */
-    protected void addToRecycleBin(IndexedDiskElementDescriptor ded)
-    {
-        // reuse the spot
-        if (ded != null)
-        {
-            storageLock.readLock().lock();
-
-            try
-            {
-                adjustBytesFree(ded, true);
-
-                if (doRecycle)
-                {
-                    recycle.add(ded);
-                    log.debug("{0}: recycled ded {1}", logCacheName, ded);
-                }
-            }
-            finally
-            {
-                storageLock.readLock().unlock();
-            }
-        }
-    }
-
-    /**
-     * Performs the check for optimization, and if it is required, do it.
-     */
-    protected void doOptimizeRealTime()
-    {
-        if (isRealTimeOptimizationEnabled && !isOptimizing
-            && removeCount++ >= cattr.getOptimizeAtRemoveCount())
-        {
-            isOptimizing = true;
-
-            log.info("{0}: Optimizing file. removeCount [{1}] OptimizeAtRemoveCount [{2}]",
-                    logCacheName, removeCount, cattr.getOptimizeAtRemoveCount());
-
-            if (currentOptimizationThread == null)
-            {
-                storageLock.writeLock().lock();
-
-                try
-                {
-                    if (currentOptimizationThread == null)
-                    {
-                        currentOptimizationThread = new Thread(() -> {
-                            optimizeFile();
-                            currentOptimizationThread = null;
-                        }, "IndexedDiskCache-OptimizationThread");
-                    }
-                }
-                finally
-                {
-                    storageLock.writeLock().unlock();
-                }
-
-                if (currentOptimizationThread != null)
-                {
-                    currentOptimizationThread.start();
-                }
-            }
-        }
-    }
-
-    /**
-     * File optimization is handled by this method. It works as follows:
-     * <ol>
-     * <li>Shutdown recycling and turn on queuing of puts.</li>
-     * <li>Take a snapshot of the current descriptors. If there are any removes, ignore them, as they will be compacted during the
-     * next optimization.</li>
-     * <li>Optimize the snapshot. For each descriptor:
-     * <ol>
-     * <li>Obtain the write-lock.</li>
-     * <li>Shift the element on the disk, in order to compact out the free space.</li>
-     * <li>Release the write-lock. This allows elements to still be accessible during optimization.</li>
-     * </ol>
-     * </li>
-     * <li>Obtain the write-lock.</li>
-     * <li>All queued puts are made at the end of the file. Optimize these under a single write-lock.</li>
-     * <li>Truncate the file.</li>
-     * <li>Release the write-lock.</li>
-     * <li>Restore system to standard operation.</li>
-     * </ol>
-     */
-    protected void optimizeFile()
-    {
-        ElapsedTimer timer = new ElapsedTimer();
-        timesOptimized++;
-        log.info("{0}: Beginning Optimization #{1}", logCacheName, timesOptimized);
-
-        // CREATE SNAPSHOT
-        IndexedDiskElementDescriptor[] defragList = null;
-
-        storageLock.writeLock().lock();
-
-        try
-        {
-            queueInput = true;
-            // shut off recycle while we're optimizing,
-            doRecycle = false;
-            defragList = createPositionSortedDescriptorList();
-        }
-        finally
-        {
-            storageLock.writeLock().unlock();
-        }
-
-        // Defrag the file outside of the write lock. This allows a move to be made,
-        // and yet have the element still accessible for reading or writing.
-        long expectedNextPos = defragFile(defragList, 0);
-
-        // ADD THE QUEUED ITEMS to the end and then truncate
-        storageLock.writeLock().lock();
-
-        try
-        {
-            try
-            {
-                if (!queuedPutList.isEmpty())
-                {
-                    defragList = queuedPutList.toArray(new IndexedDiskElementDescriptor[queuedPutList.size()]);
-
-                    // pack them at the end
-                    expectedNextPos = defragFile(defragList, expectedNextPos);
-                }
-                // TRUNCATE THE FILE
-                dataFile.truncate(expectedNextPos);
-            }
-            catch (IOException e)
-            {
-                log.error("{0}: Error optimizing queued puts.", logCacheName, e);
-            }
-
-            // RESTORE NORMAL OPERATION
-            removeCount = 0;
-            resetBytesFree();
-            this.recycle.clear();
-            queuedPutList.clear();
-            queueInput = false;
-            // turn recycle back on.
-            doRecycle = true;
-            isOptimizing = false;
-        }
-        finally
-        {
-            storageLock.writeLock().unlock();
-        }
-
-        log.info("{0}: Finished #{1}, Optimization took {2}",
-                logCacheName, timesOptimized, timer.getElapsedTimeString());
-    }
-
-    /**
-     * Defragments the file in place by compacting out the free space (i.e., moving records
-     * forward). If there were no gaps the resulting file would be the same size as the previous
-     * file. This must be supplied an ordered defragList.
-     * <p>
-     *
-     * @param defragList
-     *            sorted list of descriptors for optimization
-     * @param startingPos
-     *            the start position in the file
-     * @return this is the potential new file end
-     */
-    private long defragFile(IndexedDiskElementDescriptor[] defragList, long startingPos)
-    {
-        ElapsedTimer timer = new ElapsedTimer();
-        long preFileSize = 0;
-        long postFileSize = 0;
-        long expectedNextPos = 0;
-        try
-        {
-            preFileSize = this.dataFile.length();
-            // find the first gap in the disk and start defragging.
-            expectedNextPos = startingPos;
-            for (int i = 0; i < defragList.length; i++)
-            {
-                storageLock.writeLock().lock();
-                try
-                {
-                    if (expectedNextPos != defragList[i].pos)
-                    {
-                        dataFile.move(defragList[i], expectedNextPos);
-                    }
-                    expectedNextPos = defragList[i].pos + IndexedDisk.HEADER_SIZE_BYTES + defragList[i].len;
-                }
-                finally
-                {
-                    storageLock.writeLock().unlock();
-                }
-            }
-
-            postFileSize = this.dataFile.length();
-
-            // this is the potential new file end
-            return expectedNextPos;
-        }
-        catch (IOException e)
-        {
-            log.error("{0}: Error occurred during defragmentation.", logCacheName, e);
-        }
-        finally
-        {
-            log.info("{0}: Defragmentation took {1}. File Size (before={2}) (after={3}) (truncating to {4})",
-                    logCacheName, timer.getElapsedTimeString(), preFileSize, postFileSize, expectedNextPos);
-        }
-
-        return 0;
-    }
-
-    /**
-     * Creates a snapshot of the IndexedDiskElementDescriptors in the keyHash and returns them
-     * sorted by position in the dataFile.
-     * <p>
-     *
-     * @return IndexedDiskElementDescriptor[]
-     */
-    private IndexedDiskElementDescriptor[] createPositionSortedDescriptorList()
-    {
-        List<IndexedDiskElementDescriptor> defragList = new ArrayList<>(keyHash.values());
-        Collections.sort(defragList, new PositionComparator());
-
-        return defragList.toArray(new IndexedDiskElementDescriptor[0]);
-    }
-
-    /**
-     * Returns the current cache size.
-     * <p>
-     *
-     * @return The size value
-     */
-    @Override
-    public int getSize()
-    {
-        return keyHash.size();
-    }
-
-    /**
-     * Returns the size of the recycle bin in number of elements.
-     * <p>
-     *
-     * @return The number of items in the bin.
-     */
-    protected int getRecyleBinSize()
-    {
-        return this.recycle.size();
-    }
-
-    /**
-     * Returns the number of times we have used spots from the recycle bin.
-     * <p>
-     *
-     * @return The number of spots used.
-     */
-    protected int getRecyleCount()
-    {
-        return this.recycleCnt;
-    }
-
-    /**
-     * Returns the number of bytes that are free. When an item is removed, its length is recorded.
-     * When a spot is used form the recycle bin, the length of the item stored is recorded.
-     * <p>
-     *
-     * @return The number bytes free on the disk file.
-     */
-    protected long getBytesFree()
-    {
-        return this.bytesFree.get();
-    }
-
-    /**
-     * Resets the number of bytes that are free.
-     */
-    private void resetBytesFree()
-    {
-        this.bytesFree.set(0);
-    }
-
-    /**
-     * To subtract you can pass in false for add..
-     * <p>
-     *
-     * @param ded
-     * @param add
-     */
-    private void adjustBytesFree(IndexedDiskElementDescriptor ded, boolean add)
-    {
-        if (ded != null)
-        {
-            int amount = ded.len + IndexedDisk.HEADER_SIZE_BYTES;
-
-            if (add)
-            {
-                this.bytesFree.addAndGet(amount);
-            }
-            else
-            {
-                this.bytesFree.addAndGet(-amount);
-            }
-        }
-    }
-
-    /**
-     * This is for debugging and testing.
-     * <p>
-     *
-     * @return the length of the data file.
-     * @throws IOException
-     */
-    protected long getDataFileSize() throws IOException
-    {
-        long size = 0;
-
-        storageLock.readLock().lock();
-
-        try
-        {
-            if (dataFile != null)
-            {
-                size = dataFile.length();
-            }
-        }
-        finally
-        {
-            storageLock.readLock().unlock();
-        }
-
-        return size;
-    }
-
-    /**
-     * For debugging. This dumps the values by default.
-     */
-    public void dump()
-    {
-        dump(true);
-    }
-
-    /**
-     * For debugging.
-     * <p>
-     *
-     * @param dumpValues
-     *            A boolean indicating if values should be dumped.
-     */
-    public void dump(boolean dumpValues)
-    {
-        if (log.isTraceEnabled())
-        {
-            log.trace("{0}: [dump] Number of keys: {1}", logCacheName, keyHash.size());
-
-            for (Map.Entry<K, IndexedDiskElementDescriptor> e : keyHash.entrySet())
-            {
-                K key = e.getKey();
-                IndexedDiskElementDescriptor ded = e.getValue();
-
-                log.trace("{0}: [dump] Disk element, key: {1}, pos: {2}, len: {3}" +
-                        (dumpValues ? ", val: " + get(key) : ""),
-                        logCacheName, key, ded.pos, ded.len);
-            }
-        }
-    }
-
-    /**
-     * @return Returns the AuxiliaryCacheAttributes.
-     */
-    @Override
-    public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
-    {
-        return this.cattr;
-    }
-
-    /**
-     * Returns info about the disk cache.
-     * <p>
-     *
-     * @see org.apache.commons.jcs.auxiliary.AuxiliaryCache#getStatistics()
-     */
-    @Override
-    public synchronized IStats getStatistics()
-    {
-        IStats stats = new Stats();
-        stats.setTypeName("Indexed Disk Cache");
-
-        ArrayList<IStatElement<?>> elems = new ArrayList<>();
-
-        elems.add(new StatElement<>("Is Alive", Boolean.valueOf(isAlive())));
-        elems.add(new StatElement<>("Key Map Size", Integer.valueOf(this.keyHash != null ? this.keyHash.size() : -1)));
-        try
-        {
-            elems.add(
-                    new StatElement<>("Data File Length", Long.valueOf(this.dataFile != null ? this.dataFile.length() : -1L)));
-        }
-        catch (IOException e)
-        {
-            log.error(e);
-        }
-        elems.add(new StatElement<>("Max Key Size", this.maxKeySize));
-        elems.add(new StatElement<>("Hit Count", this.hitCount));
-        elems.add(new StatElement<>("Bytes Free", this.bytesFree));
-        elems.add(new StatElement<>("Optimize Operation Count", Integer.valueOf(this.removeCount)));
-        elems.add(new StatElement<>("Times Optimized", Integer.valueOf(this.timesOptimized)));
-        elems.add(new StatElement<>("Recycle Count", Integer.valueOf(this.recycleCnt)));
-        elems.add(new StatElement<>("Recycle Bin Size", Integer.valueOf(this.recycle.size())));
-        elems.add(new StatElement<>("Startup Size", Integer.valueOf(this.startupSize)));
-
-        // get the stats from the super too
-        IStats sStats = super.getStatistics();
-        elems.addAll(sStats.getStatElements());
-
-        stats.setStatElements(elems);
-
-        return stats;
-    }
-
-    /**
-     * This is exposed for testing.
-     * <p>
-     *
-     * @return Returns the timesOptimized.
-     */
-    protected int getTimesOptimized()
-    {
-        return timesOptimized;
-    }
-
-    /**
-     * This is used by the event logging.
-     * <p>
-     *
-     * @return the location of the disk, either path or ip.
-     */
-    @Override
-    protected String getDiskLocation()
-    {
-        return dataFile.getFilePath();
-    }
-
-    /**
-     * Compares IndexedDiskElementDescriptor based on their position.
-     * <p>
-     */
-    protected static final class PositionComparator implements Comparator<IndexedDiskElementDescriptor>, Serializable
-    {
-        /** serialVersionUID */
-        private static final long serialVersionUID = -8387365338590814113L;
-
-        /**
-         * Compares two descriptors based on position.
-         * <p>
-         *
-         * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
-         */
-        @Override
-        public int compare(IndexedDiskElementDescriptor ded1, IndexedDiskElementDescriptor ded2)
-        {
-            if (ded1.pos < ded2.pos)
-            {
-                return -1;
-            }
-            else if (ded1.pos == ded2.pos)
-            {
-                return 0;
-            }
-            else
-            {
-                return 1;
-            }
-        }
-    }
-
-    /**
-     * Class for recycling and lru. This implements the LRU overflow callback, so we can add items
-     * to the recycle bin. This class counts the size element to decide, when to throw away an element
-     */
-    public class LRUMapSizeLimited extends AbstractLRUMap<K, IndexedDiskElementDescriptor>
-    {
-        /**
-         * <code>tag</code> tells us which map we are working on.
-         */
-        public static final String TAG = "orig";
-
-        // size of the content in kB
-        private AtomicInteger contentSize;
-        private int maxSize;
-
-        /**
-         * Default
-         */
-        public LRUMapSizeLimited()
-        {
-            this(-1);
-        }
-
-        /**
-         * @param maxKeySize
-         */
-        public LRUMapSizeLimited(int maxKeySize)
-        {
-            super();
-            this.maxSize = maxKeySize;
-            this.contentSize = new AtomicInteger(0);
-        }
-
-        // keep the content size in kB, so 2^31 kB is reasonable value
-        private void subLengthFromCacheSize(IndexedDiskElementDescriptor value)
-        {
-            contentSize.addAndGet((value.len + IndexedDisk.HEADER_SIZE_BYTES) / -1024 - 1);
-        }
-
-        // keep the content size in kB, so 2^31 kB is reasonable value
-        private void addLengthToCacheSize(IndexedDiskElementDescriptor value)
-        {
-            contentSize.addAndGet((value.len + IndexedDisk.HEADER_SIZE_BYTES) / 1024 + 1);
-        }
-
-        @Override
-        public IndexedDiskElementDescriptor put(K key, IndexedDiskElementDescriptor value)
-        {
-            IndexedDiskElementDescriptor oldValue = null;
-
-            try
-            {
-                oldValue = super.put(key, value);
-            }
-            finally
-            {
-                // keep the content size in kB, so 2^31 kB is reasonable value
-                if (value != null)
-                {
-                    addLengthToCacheSize(value);
-                }
-                if (oldValue != null)
-                {
-                    subLengthFromCacheSize(oldValue);
-                }
-            }
-
-            return oldValue;
-        }
-
-        @Override
-        public IndexedDiskElementDescriptor remove(Object key)
-        {
-            IndexedDiskElementDescriptor value = null;
-
-            try
-            {
-                value = super.remove(key);
-                return value;
-            }
-            finally
-            {
-                if (value != null)
-                {
-                    subLengthFromCacheSize(value);
-                }
-            }
-        }
-
-        /**
-         * This is called when the may key size is reached. The least recently used item will be
-         * passed here. We will store the position and size of the spot on disk in the recycle bin.
-         * <p>
-         *
-         * @param key
-         * @param value
-         */
-        @Override
-        protected void processRemovedLRU(K key, IndexedDiskElementDescriptor value)
-        {
-            if (value != null)
-            {
-                subLengthFromCacheSize(value);
-            }
-
-            addToRecycleBin(value);
-
-            log.debug("{0}: Removing key: [{1}] from key store.", logCacheName, key);
-            log.debug("{0}: Key store size: [{1}].", logCacheName, this.size());
-
-            doOptimizeRealTime();
-        }
-
-        @Override
-        protected boolean shouldRemove()
-        {
-            return maxSize > 0 && contentSize.get() > maxSize && this.size() > 0;
-        }
-    }
-
-    /**
-     * Class for recycling and lru. This implements the LRU overflow callback, so we can add items
-     * to the recycle bin. This class counts the elements to decide, when to throw away an element
-     */
-
-    public class LRUMapCountLimited extends LRUMap<K, IndexedDiskElementDescriptor>
-    // implements Serializable
-    {
-        public LRUMapCountLimited(int maxKeySize)
-        {
-            super(maxKeySize);
-        }
-
-        /**
-         * This is called when the may key size is reached. The least recently used item will be
-         * passed here. We will store the position and size of the spot on disk in the recycle bin.
-         * <p>
-         *
-         * @param key
-         * @param value
-         */
-        @Override
-        protected void processRemovedLRU(K key, IndexedDiskElementDescriptor value)
-        {
-            addToRecycleBin(value);
-            log.debug("{0}: Removing key: [{1}] from key store.", logCacheName, key);
-            log.debug("{0}: Key store size: [{1}].", logCacheName, this.size());
-
-            doOptimizeRealTime();
-        }
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheAttributes.java
deleted file mode 100644
index fffaadb..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheAttributes.java
+++ /dev/null
@@ -1,154 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.indexed;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.disk.AbstractDiskCacheAttributes;
-
-/**
- * Configuration class for the Indexed Disk Cache
- */
-public class IndexedDiskCacheAttributes
-    extends AbstractDiskCacheAttributes
-{
-    /** Don't change. */
-    private static final long serialVersionUID = -2190863599358782950L;
-
-    /** default value */
-    private static final int DEFAULT_maxKeySize = 5000;
-
-    /** -1 means no limit. */
-    private int maxKeySize = DEFAULT_maxKeySize;
-
-    /** default to -1, i.e., don't optimize until shutdown */
-    private int optimizeAtRemoveCount = -1;
-
-    /** Should we optimize on shutdown. */
-    public static final boolean DEFAULT_OPTIMIZE_ON_SHUTDOWN = true;
-
-    /** Should we optimize on shutdown. */
-    private boolean optimizeOnShutdown = DEFAULT_OPTIMIZE_ON_SHUTDOWN;
-
-    /** Should we clear the disk on startup. */
-    public static final boolean DEFAULT_CLEAR_DISK_ON_STARTUP = false;
-
-    /** Should we clear the disk on startup. If true the contents of disk are cleared. */
-    private boolean clearDiskOnStartup = DEFAULT_CLEAR_DISK_ON_STARTUP;
-
-    /**
-     * Constructor for the DiskCacheAttributes object
-     */
-    public IndexedDiskCacheAttributes()
-    {
-        super();
-    }
-
-    /**
-     * Gets the maxKeySize attribute of the DiskCacheAttributes object
-     * <p>
-     * @return The maxKeySize value
-     */
-    public int getMaxKeySize()
-    {
-        return this.maxKeySize;
-    }
-
-    /**
-     * Sets the maxKeySize attribute of the DiskCacheAttributes object
-     * <p>
-     * @param maxKeySize The new maxKeySize value
-     */
-    public void setMaxKeySize( int maxKeySize )
-    {
-        this.maxKeySize = maxKeySize;
-    }
-
-    /**
-     * Gets the optimizeAtRemoveCount attribute of the DiskCacheAttributes object
-     * <p>
-     * @return The optimizeAtRemoveCount value
-     */
-    public int getOptimizeAtRemoveCount()
-    {
-        return this.optimizeAtRemoveCount;
-    }
-
-    /**
-     * Sets the optimizeAtRemoveCount attribute of the DiskCacheAttributes object This number
-     * determines how often the disk cache should run real time optimizations.
-     * <p>
-     * @param cnt The new optimizeAtRemoveCount value
-     */
-    public void setOptimizeAtRemoveCount( int cnt )
-    {
-        this.optimizeAtRemoveCount = cnt;
-    }
-
-    /**
-     * @param optimizeOnShutdown The optimizeOnShutdown to set.
-     */
-    public void setOptimizeOnShutdown( boolean optimizeOnShutdown )
-    {
-        this.optimizeOnShutdown = optimizeOnShutdown;
-    }
-
-    /**
-     * @return Returns the optimizeOnShutdown.
-     */
-    public boolean isOptimizeOnShutdown()
-    {
-        return optimizeOnShutdown;
-    }
-
-    /**
-     * @param clearDiskOnStartup the clearDiskOnStartup to set
-     */
-    public void setClearDiskOnStartup( boolean clearDiskOnStartup )
-    {
-        this.clearDiskOnStartup = clearDiskOnStartup;
-    }
-
-    /**
-     * @return the clearDiskOnStartup
-     */
-    public boolean isClearDiskOnStartup()
-    {
-        return clearDiskOnStartup;
-    }
-
-    /**
-     * Write out the values for debugging purposes.
-     * <p>
-     * @return String
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder str = new StringBuilder();
-        str.append( "IndexedDiskCacheAttributes " );
-        str.append( "\n diskPath = " + super.getDiskPath() );
-        str.append( "\n maxPurgatorySize   = " + super.getMaxPurgatorySize() );
-        str.append( "\n maxKeySize  = " + maxKeySize );
-        str.append( "\n optimizeAtRemoveCount  = " + optimizeAtRemoveCount );
-        str.append( "\n shutdownSpoolTimeLimit  = " + super.getShutdownSpoolTimeLimit() );
-        str.append( "\n optimizeOnShutdown  = " + optimizeOnShutdown );
-        str.append( "\n clearDiskOnStartup  = " + clearDiskOnStartup );
-        return str.toString();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheFactory.java
deleted file mode 100644
index 7d0822f..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheFactory.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.indexed;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.AbstractAuxiliaryCacheFactory;
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * Creates disk cache instances.
- */
-public class IndexedDiskCacheFactory
-    extends AbstractAuxiliaryCacheFactory
-{
-    /** The logger. */
-    private static final Log log = LogManager.getLog( IndexedDiskCacheFactory.class );
-
-    /**
-     * Create an instance of an IndexedDiskCache.
-     * <p>
-     * @param iaca cache attributes of this cache instance
-     * @param cacheMgr This allows auxiliaries to reference the manager without assuming that it is
-     *            a singleton. This will allow JCS to be a non-singleton. Also, it makes it easier to
-     *            test.
-     * @param cacheEventLogger
-     * @param elementSerializer
-     * @return IndexedDiskCache
-     */
-    @Override
-    public <K, V> IndexedDiskCache<K, V> createCache( AuxiliaryCacheAttributes iaca, ICompositeCacheManager cacheMgr,
-                                       ICacheEventLogger cacheEventLogger, IElementSerializer elementSerializer )
-    {
-        IndexedDiskCacheAttributes idca = (IndexedDiskCacheAttributes) iaca;
-        log.debug( "Creating DiskCache for attributes = {0}", idca );
-
-        IndexedDiskCache<K, V> cache = new IndexedDiskCache<>( idca, elementSerializer );
-        cache.setCacheEventLogger( cacheEventLogger );
-
-        return cache;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskDumper.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskDumper.java
deleted file mode 100644
index a6ab077..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskDumper.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.indexed;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.Serializable;
-
-
-/**
- * Used to dump out a Disk cache from disk for debugging. This is meant to be
- * run as a command line utility for
- */
-public class IndexedDiskDumper
-{
-    /**
-     * The main program for the DiskDumper class
-     * <p>
-     * Creates a disk cache and then calls dump, which write out the contents to
-     * a debug log.
-     * <p>
-     * @param args
-     *            The command line arguments
-     */
-    public static void main( String[] args )
-    {
-        if ( args.length != 1 )
-        {
-            System.out.println( "Usage: java org.apache.commons.jcs.auxiliary.disk.DiskDump <cache_name>" );
-            System.exit( 0 );
-        }
-
-        IndexedDiskCacheAttributes attr = new IndexedDiskCacheAttributes();
-
-        attr.setCacheName( args[0] );
-        attr.setDiskPath( args[0] );
-
-        IndexedDiskCache<Serializable, Serializable> dc = new IndexedDiskCache<>( attr );
-        dc.dump( true );
-        System.exit( 0 );
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskElementDescriptor.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskElementDescriptor.java
deleted file mode 100644
index 5f5af18..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskElementDescriptor.java
+++ /dev/null
@@ -1,132 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.indexed;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.Serializable;
-
-/**
- * Disk objects are located by descriptor entries. These are saved on shutdown and loaded into
- * memory on startup.
- */
-public class IndexedDiskElementDescriptor
-    implements Serializable, Comparable<IndexedDiskElementDescriptor>
-{
-    /** Don't change */
-    private static final long serialVersionUID = -3029163572847659450L;
-
-    /** Position of the cache data entry on disk. */
-    long pos;
-
-    /** Number of bytes the serialized form of the cache data takes. */
-    int len;
-
-    /**
-     * Constructs a usable disk element descriptor.
-     * <p>
-     * @param pos
-     * @param len
-     */
-    public IndexedDiskElementDescriptor( long pos, int len )
-    {
-        this.pos = pos;
-        this.len = len;
-    }
-
-    /**
-     * @return debug string
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder buf = new StringBuilder();
-        buf.append( "[DED: " );
-        buf.append( " pos = " + pos );
-        buf.append( " len = " + len );
-        buf.append( "]" );
-        return buf.toString();
-    }
-
-    /**
-     * @see java.lang.Object#hashCode()
-     */
-    @Override
-    public int hashCode()
-    {
-        return Long.valueOf(this.pos).hashCode() ^ Integer.valueOf(len).hashCode();
-    }
-
-    /**
-     * @see java.lang.Object#equals(java.lang.Object)
-     */
-    @Override
-    public boolean equals(Object o)
-    {
-    	if (o == null)
-    	{
-    		return false;
-    	}
-    	else if (o instanceof IndexedDiskElementDescriptor)
-        {
-    		IndexedDiskElementDescriptor ided = (IndexedDiskElementDescriptor)o;
-            return pos == ided.pos && len == ided.len;
-        }
-
-        return false;
-    }
-
-    /**
-     * Compares based on length, then on pos descending.
-     * <p>
-     * @param o Object
-     * @return int
-     */
-    @Override
-    public int compareTo( IndexedDiskElementDescriptor o )
-    {
-        if ( o == null )
-        {
-            return 1;
-        }
-
-        if ( o.len == len )
-        {
-        	if ( o.pos == pos )
-        	{
-        		return 0;
-        	}
-        	else if ( o.pos < pos )
-        	{
-        		return -1;
-        	}
-        	else
-        	{
-        		return 1;
-        	}
-        }
-        else if ( o.len > len )
-        {
-            return -1;
-        }
-        else
-        {
-            return 1;
-        }
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/JDBCDiskCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/JDBCDiskCache.java
deleted file mode 100644
index a60b81d..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/JDBCDiskCache.java
+++ /dev/null
@@ -1,878 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.jdbc;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.sql.Connection;
-import java.sql.DatabaseMetaData;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Timestamp;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import javax.sql.DataSource;
-
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes;
-import org.apache.commons.jcs.auxiliary.disk.AbstractDiskCache;
-import org.apache.commons.jcs.auxiliary.disk.jdbc.dsfactory.DataSourceFactory;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEvent;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-import org.apache.commons.jcs.engine.stats.StatElement;
-import org.apache.commons.jcs.engine.stats.behavior.IStatElement;
-import org.apache.commons.jcs.engine.stats.behavior.IStats;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-import org.apache.commons.jcs.utils.serialization.StandardSerializer;
-
-/**
- * This is the jdbc disk cache plugin.
- * <p>
- * It expects a table created by the following script. The table name is configurable.
- * <p>
- *
- * <pre>
- *                       drop TABLE JCS_STORE;
- *                       CREATE TABLE JCS_STORE
- *                       (
- *                       CACHE_KEY                  VARCHAR(250)          NOT NULL,
- *                       REGION                     VARCHAR(250)          NOT NULL,
- *                       ELEMENT                    BLOB,
- *                       CREATE_TIME                TIMESTAMP,
- *                       UPDATE_TIME_SECONDS        BIGINT,
- *                       MAX_LIFE_SECONDS           BIGINT,
- *                       SYSTEM_EXPIRE_TIME_SECONDS BIGINT,
- *                       IS_ETERNAL                 CHAR(1),
- *                       PRIMARY KEY (CACHE_KEY, REGION)
- *                       );
- * </pre>
- * <p>
- * The cleanup thread will delete non eternal items where (now - create time) &gt; max life seconds *
- * 1000
- * <p>
- * To speed up the deletion the SYSTEM_EXPIRE_TIME_SECONDS is used instead. It is recommended that
- * an index be created on this column is you will have over a million records.
- * <p>
- * @author Aaron Smuts
- */
-public class JDBCDiskCache<K, V>
-    extends AbstractDiskCache<K, V>
-{
-    /** The local logger. */
-    private static final Log log = LogManager.getLog( JDBCDiskCache.class );
-
-    /** custom serialization */
-    private IElementSerializer elementSerializer = new StandardSerializer();
-
-    /** configuration */
-    private JDBCDiskCacheAttributes jdbcDiskCacheAttributes;
-
-    /** # of times update was called */
-    private AtomicInteger updateCount = new AtomicInteger(0);
-
-    /** # of times get was called */
-    private AtomicInteger getCount = new AtomicInteger(0);
-
-    /** # of times getMatching was called */
-    private AtomicInteger getMatchingCount = new AtomicInteger(0);
-
-    /** if count % interval == 0 then log */
-    private static final int LOG_INTERVAL = 100;
-
-    /** db connection pool */
-    private DataSourceFactory dsFactory = null;
-
-    /** tracks optimization */
-    private TableState tableState;
-
-    /**
-     * Constructs a JDBC Disk Cache for the provided cache attributes. The table state object is
-     * used to mark deletions.
-     * <p>
-     * @param cattr the configuration object for this cache
-     * @param dsFactory the DataSourceFactory for this cache
-     * @param tableState an object to track table operations
-     * @param compositeCacheManager the global cache manager
-     */
-    public JDBCDiskCache( JDBCDiskCacheAttributes cattr, DataSourceFactory dsFactory, TableState tableState,
-                          ICompositeCacheManager compositeCacheManager )
-    {
-        super( cattr );
-
-        setTableState( tableState );
-        setJdbcDiskCacheAttributes( cattr );
-
-        log.info( "jdbcDiskCacheAttributes = {0}", () -> getJdbcDiskCacheAttributes() );
-
-        // This initializes the pool access.
-        this.dsFactory = dsFactory;
-
-        // Initialization finished successfully, so set alive to true.
-        setAlive(true);
-    }
-
-    /**
-     * Inserts or updates. By default it will try to insert. If the item exists we will get an
-     * error. It will then update. This behavior is configurable. The cache can be configured to
-     * check before inserting.
-     * <p>
-     * @param ce
-     */
-    @Override
-    protected void processUpdate( ICacheElement<K, V> ce )
-    {
-    	updateCount.incrementAndGet();
-
-        log.debug( "updating, ce = {0}", ce );
-
-        try (Connection con = getDataSource().getConnection())
-        {
-            log.debug( "Putting [{0}] on disk.",  () -> ce.getKey());
-
-            byte[] element;
-
-            try
-            {
-                element = getElementSerializer().serialize( ce );
-            }
-            catch ( IOException e )
-            {
-                log.error( "Could not serialize element", e );
-                return;
-            }
-
-            insertOrUpdate( ce, con, element );
-        }
-        catch ( SQLException e )
-        {
-            log.error( "Problem getting connection.", e );
-        }
-
-        if ( log.isInfoEnabled() )
-        {
-            if ( updateCount.get() % LOG_INTERVAL == 0 )
-            {
-                // TODO make a log stats method
-                log.info( "Update Count [{0}]", updateCount);
-            }
-        }
-    }
-
-    /**
-     * If test before insert it true, we check to see if the element exists. If the element exists
-     * we will update. Otherwise, we try inserting.  If this fails because the item exists, we will
-     * update.
-     * <p>
-     * @param ce
-     * @param con
-     * @param element
-     */
-    private void insertOrUpdate( ICacheElement<K, V> ce, Connection con, byte[] element )
-    {
-        boolean exists = false;
-
-        // First do a query to determine if the element already exists
-        if ( this.getJdbcDiskCacheAttributes().isTestBeforeInsert() )
-        {
-            exists = doesElementExist( ce, con );
-        }
-
-        // If it doesn't exist, insert it, otherwise update
-        if ( !exists )
-        {
-            exists = insertRow( ce, con, element );
-        }
-
-        // update if it exists.
-        if ( exists )
-        {
-            updateRow( ce, con, element );
-        }
-    }
-
-    /**
-     * This inserts a new row in the database.
-     * <p>
-     * @param ce
-     * @param con
-     * @param element
-     * @return true if the insertion fails because the record exists.
-     */
-    private boolean insertRow( ICacheElement<K, V> ce, Connection con, byte[] element )
-    {
-        boolean exists = false;
-        String sqlI = "insert into "
-                + getJdbcDiskCacheAttributes().getTableName()
-                + " (CACHE_KEY, REGION, ELEMENT, MAX_LIFE_SECONDS, IS_ETERNAL, CREATE_TIME, UPDATE_TIME_SECONDS, SYSTEM_EXPIRE_TIME_SECONDS) "
-                + " values (?, ?, ?, ?, ?, ?, ?, ?)";
-
-        try (PreparedStatement psInsert = con.prepareStatement( sqlI ))
-        {
-            psInsert.setString( 1, (String) ce.getKey() );
-            psInsert.setString( 2, this.getCacheName() );
-            psInsert.setBytes( 3, element );
-            psInsert.setLong( 4, ce.getElementAttributes().getMaxLife() );
-            if ( ce.getElementAttributes().getIsEternal() )
-            {
-                psInsert.setString( 5, "T" );
-            }
-            else
-            {
-                psInsert.setString( 5, "F" );
-            }
-            Timestamp createTime = new Timestamp( ce.getElementAttributes().getCreateTime() );
-            psInsert.setTimestamp( 6, createTime );
-
-            long now = System.currentTimeMillis() / 1000;
-            psInsert.setLong( 7, now );
-
-            long expireTime = now + ce.getElementAttributes().getMaxLife();
-            psInsert.setLong( 8, expireTime );
-
-            psInsert.execute();
-        }
-        catch ( SQLException e )
-        {
-            if ("23000".equals(e.getSQLState()))
-            {
-                exists = true;
-            }
-            else
-            {
-                log.error( "Could not insert element", e );
-            }
-
-            // see if it exists, if we didn't already
-            if ( !exists && !this.getJdbcDiskCacheAttributes().isTestBeforeInsert() )
-            {
-                exists = doesElementExist( ce, con );
-            }
-        }
-
-        return exists;
-    }
-
-    /**
-     * This updates a row in the database.
-     * <p>
-     * @param ce
-     * @param con
-     * @param element
-     */
-    private void updateRow( ICacheElement<K, V> ce, Connection con, byte[] element )
-    {
-        String sqlU = "update " + getJdbcDiskCacheAttributes().getTableName()
-                + " set ELEMENT  = ?, CREATE_TIME = ?, UPDATE_TIME_SECONDS = ?, " + " SYSTEM_EXPIRE_TIME_SECONDS = ? "
-                + " where CACHE_KEY = ? and REGION = ?";
-
-        try (PreparedStatement psUpdate = con.prepareStatement( sqlU ))
-        {
-            psUpdate.setBytes( 1, element );
-
-            Timestamp createTime = new Timestamp( ce.getElementAttributes().getCreateTime() );
-            psUpdate.setTimestamp( 2, createTime );
-
-            long now = System.currentTimeMillis() / 1000;
-            psUpdate.setLong( 3, now );
-
-            long expireTime = now + ce.getElementAttributes().getMaxLife();
-            psUpdate.setLong( 4, expireTime );
-
-            psUpdate.setString( 5, (String) ce.getKey() );
-            psUpdate.setString( 6, this.getCacheName() );
-            psUpdate.execute();
-
-            log.debug( "ran update {0}", sqlU );
-        }
-        catch ( SQLException e )
-        {
-            log.error( "Error executing update sql [{0}]", sqlU, e );
-        }
-    }
-
-    /**
-     * Does an element exist for this key?
-     * <p>
-     * @param ce the cache element
-     * @param con a database connection
-     * @return boolean
-     */
-    protected boolean doesElementExist( ICacheElement<K, V> ce, Connection con )
-    {
-        boolean exists = false;
-        // don't select the element, since we want this to be fast.
-        String sqlS = "select CACHE_KEY from " + getJdbcDiskCacheAttributes().getTableName()
-            + " where REGION = ? and CACHE_KEY = ?";
-
-        try (PreparedStatement psSelect = con.prepareStatement( sqlS ))
-        {
-            psSelect.setString( 1, this.getCacheName() );
-            psSelect.setString( 2, (String) ce.getKey() );
-
-            try (ResultSet rs = psSelect.executeQuery())
-            {
-                exists = rs.next();
-            }
-
-            log.debug( "[{0}] existing status is {1}", ce.getKey(), exists );
-        }
-        catch ( SQLException e )
-        {
-            log.error( "Problem looking for item before insert.", e );
-        }
-
-        return exists;
-    }
-
-    /**
-     * Queries the database for the value. If it gets a result, the value is deserialized.
-     * <p>
-     * @param key
-     * @return ICacheElement
-     * @see org.apache.commons.jcs.auxiliary.disk.AbstractDiskCache#get(Object)
-     */
-    @Override
-    protected ICacheElement<K, V> processGet( K key )
-    {
-    	getCount.incrementAndGet();
-
-        log.debug( "Getting [{0}] from disk", key );
-
-        if ( !isAlive() )
-        {
-            return null;
-        }
-
-        ICacheElement<K, V> obj = null;
-
-        byte[] data = null;
-        try
-        {
-            // region, key
-            String selectString = "select ELEMENT from " + getJdbcDiskCacheAttributes().getTableName()
-                + " where REGION = ? and CACHE_KEY = ?";
-
-            try (Connection con = getDataSource().getConnection())
-            {
-                try (PreparedStatement psSelect = con.prepareStatement( selectString ))
-                {
-                    psSelect.setString( 1, this.getCacheName() );
-                    psSelect.setString( 2, key.toString() );
-
-                    try (ResultSet rs = psSelect.executeQuery())
-                    {
-                        if ( rs.next() )
-                        {
-                            data = rs.getBytes( 1 );
-                        }
-                        if ( data != null )
-                        {
-                            try
-                            {
-                                // USE THE SERIALIZER
-                                obj = getElementSerializer().deSerialize( data, null );
-                            }
-                            catch ( Exception e )
-                            {
-                                log.error( "Problem getting item for key [{0}]", key, e );
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        catch ( SQLException sqle )
-        {
-            log.error( "Caught a SQL exception trying to get the item for key [{0}]",
-                    key, sqle );
-        }
-
-        if ( log.isInfoEnabled() )
-        {
-            if ( getCount.get() % LOG_INTERVAL == 0 )
-            {
-                // TODO make a log stats method
-                log.info( "Get Count [{0}]", getCount );
-            }
-        }
-        return obj;
-    }
-
-    /**
-     * This will run a like query. It will try to construct a usable query but different
-     * implementations will be needed to adjust the syntax.
-     * <p>
-     * @param pattern
-     * @return key,value map
-     */
-    @Override
-    protected Map<K, ICacheElement<K, V>> processGetMatching( String pattern )
-    {
-    	getMatchingCount.incrementAndGet();
-
-        log.debug( "Getting [{0}] from disk", pattern);
-
-        if ( !isAlive() )
-        {
-            return null;
-        }
-
-        Map<K, ICacheElement<K, V>> results = new HashMap<>();
-
-        try
-        {
-            // region, key
-            String selectString = "select CACHE_KEY, ELEMENT from " + getJdbcDiskCacheAttributes().getTableName()
-                + " where REGION = ? and CACHE_KEY like ?";
-
-            try (Connection con = getDataSource().getConnection())
-            {
-                try (PreparedStatement psSelect = con.prepareStatement( selectString ))
-                {
-                    psSelect.setString( 1, this.getCacheName() );
-                    psSelect.setString( 2, constructLikeParameterFromPattern( pattern ) );
-
-                    try (ResultSet rs = psSelect.executeQuery())
-                    {
-                        while ( rs.next() )
-                        {
-                            String key = rs.getString( 1 );
-                            byte[] data = rs.getBytes( 2 );
-                            if ( data != null )
-                            {
-                                try
-                                {
-                                    // USE THE SERIALIZER
-                                    ICacheElement<K, V> value = getElementSerializer().deSerialize( data, null );
-                                    results.put( (K) key, value );
-                                }
-                                catch ( Exception e )
-                                {
-                                    log.error( "Problem getting items for pattern [{0}]", pattern, e );
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        catch ( SQLException sqle )
-        {
-            log.error( "Caught a SQL exception trying to get items for pattern [{0}]",
-                    pattern, sqle );
-        }
-
-        if ( log.isInfoEnabled() )
-        {
-            if ( getMatchingCount.get() % LOG_INTERVAL == 0 )
-            {
-                // TODO make a log stats method
-                log.info( "Get Matching Count [{0}]", getMatchingCount);
-            }
-        }
-        return results;
-    }
-
-    /**
-     * @param pattern
-     * @return String to use in the like query.
-     */
-    public String constructLikeParameterFromPattern( String pattern )
-    {
-        String likePattern = pattern.replaceAll( "\\.\\+", "%" );
-        likePattern = likePattern.replaceAll( "\\.", "_" );
-
-        log.debug( "pattern = [{0}]", likePattern );
-
-        return likePattern;
-    }
-
-    /**
-     * Returns true if the removal was successful; or false if there is nothing to remove. Current
-     * implementation always results in a disk orphan.
-     * <p>
-     * @param key
-     * @return boolean
-     */
-    @Override
-    protected boolean processRemove( K key )
-    {
-        // remove single item.
-        String sql = "delete from " + getJdbcDiskCacheAttributes().getTableName()
-            + " where REGION = ? and CACHE_KEY = ?";
-
-        try (Connection con = getDataSource().getConnection())
-        {
-            boolean partial = false;
-            if ( key instanceof String && key.toString().endsWith( NAME_COMPONENT_DELIMITER ) )
-            {
-                // remove all keys of the same name group.
-                sql = "delete from " + getJdbcDiskCacheAttributes().getTableName()
-                    + " where REGION = ? and CACHE_KEY like ?";
-                partial = true;
-            }
-
-            try (PreparedStatement psSelect = con.prepareStatement( sql ))
-            {
-                psSelect.setString( 1, this.getCacheName() );
-                if ( partial )
-                {
-                    psSelect.setString( 2, key.toString() + "%" );
-                }
-                else
-                {
-                    psSelect.setString( 2, key.toString() );
-                }
-
-                psSelect.executeUpdate();
-
-                setAlive(true);
-            }
-            catch ( SQLException e )
-            {
-                log.error( "Problem creating statement. sql [{0}]", sql, e );
-                setAlive(false);
-            }
-        }
-        catch ( SQLException e )
-        {
-            log.error( "Problem updating cache.", e );
-            reset();
-        }
-        return false;
-    }
-
-    /**
-     * This should remove all elements. The auxiliary can be configured to forbid this behavior. If
-     * remove all is not allowed, the method balks.
-     */
-    @Override
-    protected void processRemoveAll()
-    {
-        // it should never get here from the abstract disk cache.
-        if ( this.jdbcDiskCacheAttributes.isAllowRemoveAll() )
-        {
-            try (Connection con = getDataSource().getConnection())
-            {
-                String sql = "delete from " + getJdbcDiskCacheAttributes().getTableName() + " where REGION = ?";
-
-                try (PreparedStatement psDelete = con.prepareStatement( sql ))
-                {
-                    psDelete.setString( 1, this.getCacheName() );
-                    setAlive(true);
-                    psDelete.executeUpdate();
-                }
-                catch ( SQLException e )
-                {
-                    log.error( "Problem creating statement.", e );
-                    setAlive(false);
-                }
-            }
-            catch ( SQLException e )
-            {
-                log.error( "Problem removing all.", e );
-                reset();
-            }
-        }
-        else
-        {
-            log.info( "RemoveAll was requested but the request was not fulfilled: "
-                    + "allowRemoveAll is set to false." );
-        }
-    }
-
-    /**
-     * Removed the expired. (now - create time) &gt; max life seconds * 1000
-     * <p>
-     * @return the number deleted
-     */
-    protected int deleteExpired()
-    {
-        int deleted = 0;
-
-        try (Connection con = getDataSource().getConnection())
-        {
-            // The shrinker thread might kick in before the table is created
-            // So check if the table exists first
-            DatabaseMetaData dmd = con.getMetaData();
-            ResultSet result = dmd.getTables(null, null,
-                    getJdbcDiskCacheAttributes().getTableName(), null);
-
-            if (result.next())
-            {
-                getTableState().setState( TableState.DELETE_RUNNING );
-                long now = System.currentTimeMillis() / 1000;
-
-                String sql = "delete from " + getJdbcDiskCacheAttributes().getTableName()
-                    + " where IS_ETERNAL = ? and REGION = ? and ? > SYSTEM_EXPIRE_TIME_SECONDS";
-
-                try (PreparedStatement psDelete = con.prepareStatement( sql ))
-                {
-                    psDelete.setString( 1, "F" );
-                    psDelete.setString( 2, this.getCacheName() );
-                    psDelete.setLong( 3, now );
-
-                    setAlive(true);
-
-                    deleted = psDelete.executeUpdate();
-                }
-                catch ( SQLException e )
-                {
-                    log.error( "Problem creating statement.", e );
-                    setAlive(false);
-                }
-
-                logApplicationEvent( getAuxiliaryCacheAttributes().getName(), "deleteExpired",
-                                     "Deleted expired elements.  URL: " + getDiskLocation() );
-            }
-            else
-            {
-                log.warn( "Trying to shrink non-existing table [{0}]",
-                        getJdbcDiskCacheAttributes().getTableName() );
-            }
-        }
-        catch ( SQLException e )
-        {
-            logError( getAuxiliaryCacheAttributes().getName(), "deleteExpired",
-                    e.getMessage() + " URL: " + getDiskLocation() );
-            log.error( "Problem removing expired elements from the table.", e );
-            reset();
-        }
-        finally
-        {
-            getTableState().setState( TableState.FREE );
-        }
-
-        return deleted;
-    }
-
-    /**
-     * Typically this is used to handle errors by last resort, force content update, or removeall
-     */
-    public void reset()
-    {
-        // nothing
-    }
-
-    /** Shuts down the pool */
-    @Override
-    public void processDispose()
-    {
-        ICacheEvent<K> cacheEvent = createICacheEvent( getCacheName(), (K)"none", ICacheEventLogger.DISPOSE_EVENT );
-
-        try
-        {
-        	dsFactory.close();
-        }
-        catch ( SQLException e )
-        {
-            log.error( "Problem shutting down.", e );
-        }
-        finally
-        {
-            logICacheEvent( cacheEvent );
-        }
-    }
-
-    /**
-     * Returns the current cache size. Just does a count(*) for the region.
-     * <p>
-     * @return The size value
-     */
-    @Override
-    public int getSize()
-    {
-        int size = 0;
-
-        // region, key
-        String selectString = "select count(*) from " + getJdbcDiskCacheAttributes().getTableName()
-            + " where REGION = ?";
-
-        try (Connection con = getDataSource().getConnection())
-        {
-            try (PreparedStatement psSelect = con.prepareStatement( selectString ))
-            {
-                psSelect.setString( 1, this.getCacheName() );
-
-                try (ResultSet rs = psSelect.executeQuery())
-                {
-                    if ( rs.next() )
-                    {
-                        size = rs.getInt( 1 );
-                    }
-                }
-            }
-        }
-        catch ( SQLException e )
-        {
-            log.error( "Problem getting size.", e );
-        }
-
-        return size;
-    }
-
-    /**
-     * Return the keys in this cache.
-     * <p>
-     * @see org.apache.commons.jcs.auxiliary.disk.AbstractDiskCache#getKeySet()
-     */
-    @Override
-    public Set<K> getKeySet() throws IOException
-    {
-        throw new UnsupportedOperationException( "Groups not implemented." );
-        // return null;
-    }
-
-    /**
-     * @param elementSerializer The elementSerializer to set.
-     */
-    @Override
-    public void setElementSerializer( IElementSerializer elementSerializer )
-    {
-        this.elementSerializer = elementSerializer;
-    }
-
-    /**
-     * @return Returns the elementSerializer.
-     */
-    @Override
-    public IElementSerializer getElementSerializer()
-    {
-        return elementSerializer;
-    }
-
-    /**
-     * @param jdbcDiskCacheAttributes The jdbcDiskCacheAttributes to set.
-     */
-    protected void setJdbcDiskCacheAttributes( JDBCDiskCacheAttributes jdbcDiskCacheAttributes )
-    {
-        this.jdbcDiskCacheAttributes = jdbcDiskCacheAttributes;
-    }
-
-    /**
-     * @return Returns the jdbcDiskCacheAttributes.
-     */
-    protected JDBCDiskCacheAttributes getJdbcDiskCacheAttributes()
-    {
-        return jdbcDiskCacheAttributes;
-    }
-
-    /**
-     * @return Returns the AuxiliaryCacheAttributes.
-     */
-    @Override
-    public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
-    {
-        return this.getJdbcDiskCacheAttributes();
-    }
-
-    /**
-     * Extends the parent stats.
-     * <p>
-     * @return IStats
-     */
-    @Override
-    public IStats getStatistics()
-    {
-        IStats stats = super.getStatistics();
-        stats.setTypeName( "JDBC/Abstract Disk Cache" );
-
-        List<IStatElement<?>> elems = stats.getStatElements();
-
-        elems.add(new StatElement<>( "Update Count", updateCount ) );
-        elems.add(new StatElement<>( "Get Count", getCount ) );
-        elems.add(new StatElement<>( "Get Matching Count", getMatchingCount ) );
-        elems.add(new StatElement<>( "DB URL", getJdbcDiskCacheAttributes().getUrl()) );
-
-        stats.setStatElements( elems );
-
-        return stats;
-    }
-
-    /**
-     * Returns the name of the table.
-     * <p>
-     * @return the table name or UNDEFINED
-     */
-    protected String getTableName()
-    {
-        String name = "UNDEFINED";
-        if ( this.getJdbcDiskCacheAttributes() != null )
-        {
-            name = this.getJdbcDiskCacheAttributes().getTableName();
-        }
-        return name;
-    }
-
-    /**
-     * @param tableState The tableState to set.
-     */
-    public void setTableState( TableState tableState )
-    {
-        this.tableState = tableState;
-    }
-
-    /**
-     * @return Returns the tableState.
-     */
-    public TableState getTableState()
-    {
-        return tableState;
-    }
-
-    /**
-     * This is used by the event logging.
-     * <p>
-     * @return the location of the disk, either path or ip.
-     */
-    @Override
-    protected String getDiskLocation()
-    {
-        return this.jdbcDiskCacheAttributes.getUrl();
-    }
-
-    /**
-     * Public so managers can access it.
-     * @return the dsFactory
-     * @throws SQLException if getting a data source fails
-     */
-    public DataSource getDataSource() throws SQLException
-    {
-        return dsFactory.getDataSource();
-    }
-
-    /**
-     * For debugging.
-     * <p>
-     * @return this.getStats();
-     */
-    @Override
-    public String toString()
-    {
-        return this.getStats();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/JDBCDiskCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/JDBCDiskCacheAttributes.java
deleted file mode 100644
index d14202a..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/JDBCDiskCacheAttributes.java
+++ /dev/null
@@ -1,331 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.jdbc;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.disk.AbstractDiskCacheAttributes;
-
-/**
- * The configurator will set these values based on what is in the cache.ccf file.
- * <p>
- * @author Aaron Smuts
- */
-public class JDBCDiskCacheAttributes
-    extends AbstractDiskCacheAttributes
-{
-    /** Don't change */
-    private static final long serialVersionUID = -6535808344813320062L;
-
-    /** default */
-    private static final String DEFAULT_TABLE_NAME = "JCS_STORE";
-
-    /** DB username */
-    private String userName;
-
-    /** DB password */
-    private String password;
-
-    /** URL for the db */
-    private String url;
-
-    /** The name of the database. */
-    private String database = "";
-
-    /** The driver */
-    private String driverClassName;
-
-    /** The JNDI path. */
-    private String jndiPath;
-
-    /** The time between two JNDI lookups */
-    private long jndiTTL = 0L;
-
-    /** The table name */
-    private String tableName = DEFAULT_TABLE_NAME;
-
-    /** If false we will insert and if it fails we will update. */
-    private boolean testBeforeInsert = true;
-
-    /** This is the default limit on the maximum number of active connections. */
-    public static final int DEFAULT_MAX_TOTAL = 10;
-
-    /** Max connections allowed */
-    private int maxTotal = DEFAULT_MAX_TOTAL;
-
-    /** This is the default setting for the cleanup routine. */
-    public static final int DEFAULT_SHRINKER_INTERVAL_SECONDS = 300;
-
-    /** How often should we remove expired. */
-    private int shrinkerIntervalSeconds = DEFAULT_SHRINKER_INTERVAL_SECONDS;
-
-    /** Should we remove expired in the background. */
-    private boolean useDiskShrinker = true;
-
-    /** The default Pool Name to which the connection pool will be keyed. */
-    public static final String DEFAULT_POOL_NAME = "jcs";
-
-    /**
-     * If a pool name is supplied, the manager will attempt to load it. It should be configured in a
-     * separate section as follows. Assuming the name is "MyPool":
-     *
-     * <pre>
-     * jcs.jdbcconnectionpool.MyPool.attributes.userName=MyUserName
-     * jcs.jdbcconnectionpool.MyPool.attributes.password=MyPassword
-     * jcs.jdbcconnectionpool.MyPool.attributes.url=MyUrl
-     * jcs.jdbcconnectionpool.MyPool.attributes.maxActive=MyMaxActive
-     * jcs.jdbcconnectionpool.MyPool.attributes.driverClassName=MyDriverClassName
-     * </pre>
-     */
-    private String connectionPoolName;
-
-    /**
-     * @param userName The userName to set.
-     */
-    public void setUserName( String userName )
-    {
-        this.userName = userName;
-    }
-
-    /**
-     * @return Returns the userName.
-     */
-    public String getUserName()
-    {
-        return userName;
-    }
-
-    /**
-     * @param password The password to set.
-     */
-    public void setPassword( String password )
-    {
-        this.password = password;
-    }
-
-    /**
-     * @return Returns the password.
-     */
-    public String getPassword()
-    {
-        return password;
-    }
-
-    /**
-     * @param url The url to set.
-     */
-    public void setUrl( String url )
-    {
-        this.url = url;
-    }
-
-    /**
-     * @return Returns the url.
-     */
-    public String getUrl()
-    {
-        return url;
-    }
-
-    /**
-     * This is appended to the url.
-     * @param database The database to set.
-     */
-    public void setDatabase( String database )
-    {
-        this.database = database;
-    }
-
-    /**
-     * @return Returns the database.
-     */
-    public String getDatabase()
-    {
-        return database;
-    }
-
-    /**
-     * @param driverClassName The driverClassName to set.
-     */
-    public void setDriverClassName( String driverClassName )
-    {
-        this.driverClassName = driverClassName;
-    }
-
-    /**
-     * @return Returns the driverClassName.
-     */
-    public String getDriverClassName()
-    {
-        return driverClassName;
-    }
-
-    /**
-	 * @return the jndiPath
-	 */
-	public String getJndiPath()
-	{
-		return jndiPath;
-	}
-
-	/**
-	 * @param jndiPath the jndiPath to set
-	 */
-	public void setJndiPath(String jndiPath)
-	{
-		this.jndiPath = jndiPath;
-	}
-
-	/**
-	 * @return the jndiTTL
-	 */
-	public long getJndiTTL()
-	{
-		return jndiTTL;
-	}
-
-	/**
-	 * @param jndiTTL the jndiTTL to set
-	 */
-	public void setJndiTTL(long jndiTTL)
-	{
-		this.jndiTTL = jndiTTL;
-	}
-
-	/**
-     * @param tableName The tableName to set.
-     */
-    public void setTableName( String tableName )
-    {
-        this.tableName = tableName;
-    }
-
-    /**
-     * @return Returns the tableName.
-     */
-    public String getTableName()
-    {
-        return tableName;
-    }
-
-    /**
-     * If this is true then the disk cache will check to see if the item already exists in the
-     * database. If it is false, it will try to insert. If the insert fails it will try to update.
-     * <p>
-     * @param testBeforeInsert The testBeforeInsert to set.
-     */
-    public void setTestBeforeInsert( boolean testBeforeInsert )
-    {
-        this.testBeforeInsert = testBeforeInsert;
-    }
-
-    /**
-     * @return Returns the testBeforeInsert.
-     */
-    public boolean isTestBeforeInsert()
-    {
-        return testBeforeInsert;
-    }
-
-    /**
-     * @param maxTotal The maxTotal to set.
-     */
-    public void setMaxTotal( int maxActive )
-    {
-        this.maxTotal = maxActive;
-    }
-
-    /**
-     * @return Returns the maxTotal.
-     */
-    public int getMaxTotal()
-    {
-        return maxTotal;
-    }
-
-    /**
-     * @param shrinkerIntervalSecondsArg The shrinkerIntervalSeconds to set.
-     */
-    public void setShrinkerIntervalSeconds( int shrinkerIntervalSecondsArg )
-    {
-        this.shrinkerIntervalSeconds = shrinkerIntervalSecondsArg;
-    }
-
-    /**
-     * @return Returns the shrinkerIntervalSeconds.
-     */
-    public int getShrinkerIntervalSeconds()
-    {
-        return shrinkerIntervalSeconds;
-    }
-
-    /**
-     * @param useDiskShrinker The useDiskShrinker to set.
-     */
-    public void setUseDiskShrinker( boolean useDiskShrinker )
-    {
-        this.useDiskShrinker = useDiskShrinker;
-    }
-
-    /**
-     * @return Returns the useDiskShrinker.
-     */
-    public boolean isUseDiskShrinker()
-    {
-        return useDiskShrinker;
-    }
-
-    /**
-     * @param connectionPoolName the connectionPoolName to set
-     */
-    public void setConnectionPoolName( String connectionPoolName )
-    {
-        this.connectionPoolName = connectionPoolName;
-    }
-
-    /**
-     * @return the connectionPoolName
-     */
-    public String getConnectionPoolName()
-    {
-        return connectionPoolName;
-    }
-
-    /**
-     * For debugging.
-     * <p>
-     * @return debug string with most of the properties.
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder buf = new StringBuilder();
-        buf.append( "\nJDBCCacheAttributes" );
-        buf.append( "\n UserName [" + getUserName() + "]" );
-        buf.append( "\n Url [" + getUrl() + "]" );
-        buf.append( "\n Database [" + getDatabase() + "]" );
-        buf.append( "\n DriverClassName [" + getDriverClassName() + "]" );
-        buf.append( "\n TableName [" + getTableName() + "]" );
-        buf.append( "\n TestBeforeInsert [" + isTestBeforeInsert() + "]" );
-        buf.append( "\n MaxActive [" + getMaxTotal() + "]" );
-        buf.append( "\n AllowRemoveAll [" + isAllowRemoveAll() + "]" );
-        buf.append( "\n ShrinkerIntervalSeconds [" + getShrinkerIntervalSeconds() + "]" );
-        buf.append( "\n useDiskShrinker [" + isUseDiskShrinker() + "]" );
-        return buf.toString();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/JDBCDiskCacheFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/JDBCDiskCacheFactory.java
deleted file mode 100644
index 2868e82..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/JDBCDiskCacheFactory.java
+++ /dev/null
@@ -1,266 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.jdbc;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.sql.SQLException;
-import java.util.Properties;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.commons.jcs.auxiliary.AbstractAuxiliaryCacheFactory;
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes;
-import org.apache.commons.jcs.auxiliary.disk.jdbc.dsfactory.DataSourceFactory;
-import org.apache.commons.jcs.auxiliary.disk.jdbc.dsfactory.JndiDataSourceFactory;
-import org.apache.commons.jcs.auxiliary.disk.jdbc.dsfactory.SharedPoolDataSourceFactory;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.engine.behavior.IRequireScheduler;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-import org.apache.commons.jcs.utils.config.PropertySetter;
-
-/**
- * This factory should create JDBC auxiliary caches.
- * <p>
- * @author Aaron Smuts
- */
-public class JDBCDiskCacheFactory
-    extends AbstractAuxiliaryCacheFactory
-    implements IRequireScheduler
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog( JDBCDiskCacheFactory.class );
-
-    /**
-     * A map of TableState objects to table names. Each cache has a table state object, which is
-     * used to determine if any long processes such as deletes or optimizations are running.
-     */
-    private ConcurrentMap<String, TableState> tableStates;
-
-    /** The background scheduler, one for all regions. Injected by the configurator */
-    protected ScheduledExecutorService scheduler;
-
-    /**
-     * A map of table name to shrinker threads. This allows each table to have a different setting.
-     * It assumes that there is only one jdbc disk cache auxiliary defined per table.
-     */
-    private ConcurrentMap<String, ShrinkerThread> shrinkerThreadMap;
-
-    /** Pool name to DataSourceFactories */
-    private ConcurrentMap<String, DataSourceFactory> dsFactories;
-
-    /** props prefix */
-    protected static final String POOL_CONFIGURATION_PREFIX = "jcs.jdbcconnectionpool.";
-
-    /** .attributes */
-    protected static final String ATTRIBUTE_PREFIX = ".attributes";
-
-    /**
-     * This factory method should create an instance of the jdbc cache.
-     * <p>
-     * @param rawAttr specific cache configuration attributes
-     * @param compositeCacheManager the global cache manager
-     * @param cacheEventLogger a specific logger for cache events
-     * @param elementSerializer a serializer for cache elements
-     * @return JDBCDiskCache the cache instance
-     * @throws SQLException if the cache instance could not be created
-     */
-    @Override
-    public <K, V> JDBCDiskCache<K, V> createCache( AuxiliaryCacheAttributes rawAttr,
-            ICompositeCacheManager compositeCacheManager,
-            ICacheEventLogger cacheEventLogger, IElementSerializer elementSerializer )
-            throws SQLException
-    {
-        JDBCDiskCacheAttributes cattr = (JDBCDiskCacheAttributes) rawAttr;
-        TableState tableState = getTableState( cattr.getTableName() );
-        DataSourceFactory dsFactory = getDataSourceFactory(cattr, compositeCacheManager.getConfigurationProperties());
-
-        JDBCDiskCache<K, V> cache = new JDBCDiskCache<>( cattr, dsFactory, tableState, compositeCacheManager );
-        cache.setCacheEventLogger( cacheEventLogger );
-        cache.setElementSerializer( elementSerializer );
-
-        // create a shrinker if we need it.
-        createShrinkerWhenNeeded( cattr, cache );
-
-        return cache;
-    }
-
-    /**
-     * Initialize this factory
-     */
-    @Override
-    public void initialize()
-    {
-        super.initialize();
-        this.tableStates = new ConcurrentHashMap<>();
-        this.shrinkerThreadMap = new ConcurrentHashMap<>();
-        this.dsFactories = new ConcurrentHashMap<>();
-    }
-
-    /**
-     * Dispose of this factory, clean up shared resources
-     */
-    @Override
-    public void dispose()
-    {
-        this.tableStates.clear();
-
-        for (DataSourceFactory dsFactory : this.dsFactories.values())
-        {
-        	try
-        	{
-				dsFactory.close();
-			}
-        	catch (SQLException e)
-        	{
-        		log.error("Could not close data source factory {0}", dsFactory.getName(), e);
-			}
-        }
-
-        this.dsFactories.clear();
-        this.shrinkerThreadMap.clear();
-        super.dispose();
-    }
-
-    /**
-     * Get a table state for a given table name
-     *
-     * @param tableName
-     * @return a cached instance of the table state
-     */
-    protected TableState getTableState(String tableName)
-    {
-        return tableStates.computeIfAbsent(tableName, key -> new TableState(key));
-    }
-
-    /**
-	 * @see org.apache.commons.jcs.engine.behavior.IRequireScheduler#setScheduledExecutorService(java.util.concurrent.ScheduledExecutorService)
-	 */
-	@Override
-	public void setScheduledExecutorService(ScheduledExecutorService scheduledExecutor)
-	{
-		this.scheduler = scheduledExecutor;
-	}
-
-	/**
-     * Get the scheduler service
-     *
-     * @return the scheduler
-     */
-    protected ScheduledExecutorService getScheduledExecutorService()
-    {
-        return scheduler;
-    }
-
-    /**
-     * If UseDiskShrinker is true then we will create a shrinker daemon if necessary.
-     * <p>
-     * @param cattr
-     * @param raf
-     */
-    protected void createShrinkerWhenNeeded( JDBCDiskCacheAttributes cattr, JDBCDiskCache<?, ?> raf )
-    {
-        // add cache to shrinker.
-        if ( cattr.isUseDiskShrinker() )
-        {
-            ScheduledExecutorService shrinkerService = getScheduledExecutorService();
-            ShrinkerThread shrinkerThread = shrinkerThreadMap.computeIfAbsent(cattr.getTableName(), key -> {
-                ShrinkerThread newShrinkerThread = new ShrinkerThread();
-
-                long intervalMillis = Math.max( 999, cattr.getShrinkerIntervalSeconds() * 1000 );
-                log.info( "Setting the shrinker to run every [{0}] ms. for table [{1}]",
-                        intervalMillis, key );
-                shrinkerService.scheduleAtFixedRate(newShrinkerThread, 0, intervalMillis, TimeUnit.MILLISECONDS);
-
-                return newShrinkerThread;
-            });
-
-            shrinkerThread.addDiskCacheToShrinkList( raf );
-        }
-    }
-
-    /**
-     * manages the DataSourceFactories.
-     * <p>
-     * @param cattr the cache configuration
-     * @param configProps the configuration properties object
-     * @return a DataSourceFactory
-     * @throws SQLException if a database access error occurs
-     */
-    protected DataSourceFactory getDataSourceFactory( JDBCDiskCacheAttributes cattr,
-                                                      Properties configProps ) throws SQLException
-    {
-    	String poolName = null;
-
-    	if (cattr.getConnectionPoolName() == null)
-    	{
-    		poolName = cattr.getCacheName() + "." + JDBCDiskCacheAttributes.DEFAULT_POOL_NAME;
-        }
-        else
-        {
-            poolName = cattr.getConnectionPoolName();
-        }
-
-
-    	DataSourceFactory dsFactory = this.dsFactories.computeIfAbsent(poolName, key -> {
-    	    DataSourceFactory newDsFactory;
-            JDBCDiskCacheAttributes dsConfig = null;
-
-            if (cattr.getConnectionPoolName() == null)
-            {
-                dsConfig = cattr;
-            }
-            else
-            {
-                dsConfig = new JDBCDiskCacheAttributes();
-                String dsConfigAttributePrefix = POOL_CONFIGURATION_PREFIX + key + ATTRIBUTE_PREFIX;
-                PropertySetter.setProperties( dsConfig,
-                        configProps,
-                        dsConfigAttributePrefix + "." );
-
-                dsConfig.setConnectionPoolName(key);
-            }
-
-            if ( dsConfig.getJndiPath() != null )
-            {
-                newDsFactory = new JndiDataSourceFactory();
-            }
-            else
-            {
-                newDsFactory = new SharedPoolDataSourceFactory();
-            }
-
-            try
-            {
-                newDsFactory.initialize(dsConfig);
-            }
-            catch (SQLException e)
-            {
-                throw new RuntimeException(e);
-            }
-    	    return newDsFactory;
-    	});
-
-    	return dsFactory;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/ShrinkerThread.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/ShrinkerThread.java
deleted file mode 100644
index 4e0a5d6..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/ShrinkerThread.java
+++ /dev/null
@@ -1,149 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.jdbc;
-
-import java.util.Iterator;
-import java.util.concurrent.CopyOnWriteArraySet;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-import org.apache.commons.jcs.utils.timing.ElapsedTimer;
-
-/**
- * Calls delete expired on the disk caches. The shrinker is run by a clock daemon. The shrinker
- * calls delete on each region. It pauses between calls.
- * <p>
- * @author Aaron Smuts
- */
-public class ShrinkerThread
-    implements Runnable
-{
-    /** The logger. */
-    private static final Log log = LogManager.getLog( ShrinkerThread.class );
-
-    /** A set of JDBCDiskCache objects to call deleteExpired on. */
-    private final CopyOnWriteArraySet<JDBCDiskCache<?, ?>> shrinkSet =
-            new CopyOnWriteArraySet<>();
-
-    /** Default time period to use. */
-    private static final long DEFAULT_PAUSE_BETWEEN_REGION_CALLS_MILLIS = 5000;
-
-    /**
-     * How long should we wait between calls to deleteExpired when we are iterating through the list
-     * of regions. Delete can lock the table. We want to give clients a chance to get some work
-     * done.
-     */
-    private long pauseBetweenRegionCallsMillis = DEFAULT_PAUSE_BETWEEN_REGION_CALLS_MILLIS;
-
-    /**
-     * Does nothing special.
-     */
-    protected ShrinkerThread()
-    {
-        super();
-    }
-
-    /**
-     * Adds a JDBC disk cache to the set of disk cache to shrink.
-     * <p>
-     * @param diskCache
-     */
-    public void addDiskCacheToShrinkList( JDBCDiskCache<?, ?> diskCache )
-    {
-        // the set will prevent dupes.
-        // we could also just add these to a hashmap by region name
-        // but that might cause a problem if you wanted to use two different
-        // jbdc disk caches for the same region.
-        shrinkSet.add( diskCache );
-    }
-
-    /**
-     * Calls deleteExpired on each item in the set. It pauses between each call.
-     */
-    @Override
-    public void run()
-    {
-        try
-        {
-            deleteExpiredFromAllRegisteredRegions();
-        }
-        catch ( Throwable e )
-        {
-            log.error( "Caught an exception while trying to delete expired items.", e );
-        }
-    }
-
-    /**
-     * Deletes the expired items from all the registered regions.
-     */
-    private void deleteExpiredFromAllRegisteredRegions()
-    {
-        log.info( "Running JDBC disk cache shrinker. Number of regions [{0}]",
-                () -> shrinkSet.size() );
-
-        for (Iterator<JDBCDiskCache<?, ?>> i = shrinkSet.iterator(); i.hasNext();)
-        {
-            JDBCDiskCache<?, ?> cache = i.next();
-            ElapsedTimer timer = new ElapsedTimer();
-            int deleted = cache.deleteExpired();
-
-            log.info( "Deleted [{0}] expired for region [{1}] for table [{2}] in {3} ms.",
-                    deleted, cache.getCacheName(), cache.getTableName(), timer.getElapsedTime() );
-
-            // don't pause after the last call to delete expired.
-            if ( i.hasNext() )
-            {
-                log.info( "Pausing for [{0}] ms before shrinking the next region.",
-                        this.getPauseBetweenRegionCallsMillis() );
-
-                try
-                {
-                    Thread.sleep( this.getPauseBetweenRegionCallsMillis() );
-                }
-                catch ( InterruptedException e )
-                {
-                    log.warn( "Interrupted while waiting to delete expired for the next region." );
-                }
-            }
-        }
-    }
-
-    /**
-     * How long should we wait between calls to deleteExpired when we are iterating through the list
-     * of regions.
-     * <p>
-     * @param pauseBetweenRegionCallsMillis The pauseBetweenRegionCallsMillis to set.
-     */
-    public void setPauseBetweenRegionCallsMillis( long pauseBetweenRegionCallsMillis )
-    {
-        this.pauseBetweenRegionCallsMillis = pauseBetweenRegionCallsMillis;
-    }
-
-    /**
-     * How long should we wait between calls to deleteExpired when we are iterating through the list
-     * of regions.
-     * <p>
-     * @return Returns the pauseBetweenRegionCallsMillis.
-     */
-    public long getPauseBetweenRegionCallsMillis()
-    {
-        return pauseBetweenRegionCallsMillis;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/TableState.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/TableState.java
deleted file mode 100644
index a862660..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/TableState.java
+++ /dev/null
@@ -1,114 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.jdbc;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.Serializable;
-
-/**
- * This is used by various elements of the JDBC disk cache to indicate the
- * status of a table. The MySQL disk cache, for instance, marks the status as
- * optimizing when a scheduled optimization is taking place. This allows the
- * cache to balk rather than block during long running optimizations.
- * <p>
- * @author Aaron Smuts
- */
-public class TableState
-    implements Serializable
-{
-    /** Don't change. */
-    private static final long serialVersionUID = -6625081552084964885L;
-
-    /** Name of the table whose state this reflects. */
-    private String tableName;
-
-    /**
-     * The table is free. It can be accessed and no potentially table locking
-     * jobs are running.
-     */
-    public static final int FREE = 0;
-
-    /** A potentially table locking deletion is running */
-    public static final int DELETE_RUNNING = 1;
-
-    /** A table locking optimization is running. */
-    public static final int OPTIMIZATION_RUNNING = 2;
-
-    /** we might want to add error */
-    private int state = FREE;
-
-    /**
-     * Construct a usable table state.
-     * <p>
-     * @param tableName
-     */
-    public TableState( String tableName )
-    {
-        this.setTableName( tableName );
-    }
-
-    /**
-     * @param tableName
-     *            The tableName to set.
-     */
-    public void setTableName( String tableName )
-    {
-        this.tableName = tableName;
-    }
-
-    /**
-     * @return Returns the tableName.
-     */
-    public String getTableName()
-    {
-        return tableName;
-    }
-
-    /**
-     * @param state
-     *            The state to set.
-     */
-    public void setState( int state )
-    {
-        this.state = state;
-    }
-
-    /**
-     * @return Returns the state.
-     */
-    public int getState()
-    {
-        return state;
-    }
-
-    /**
-     * Write out the values for debugging purposes.
-     * <p>
-     * @return String
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder str = new StringBuilder();
-        str.append( "TableState " );
-        str.append( "\n TableName = " + getTableName() );
-        str.append( "\n State = " + getState() );
-        return str.toString();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/dsfactory/DataSourceFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/dsfactory/DataSourceFactory.java
deleted file mode 100644
index 73b57e1..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/dsfactory/DataSourceFactory.java
+++ /dev/null
@@ -1,85 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.jdbc.dsfactory;

-

-/*

- * Licensed to the Apache Software Foundation (ASF) under one

- * or more contributor license agreements.  See the NOTICE file

- * distributed with this work for additional information

- * regarding copyright ownership.  The ASF licenses this file

- * to you under the Apache License, Version 2.0 (the

- * "License"); you may not use this file except in compliance

- * with the License.  You may obtain a copy of the License at

- *

- *   http://www.apache.org/licenses/LICENSE-2.0

- *

- * Unless required by applicable law or agreed to in writing,

- * software distributed under the License is distributed on an

- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

- * KIND, either express or implied.  See the License for the

- * specific language governing permissions and limitations

- * under the License.

- */

-

-import java.sql.SQLException;

-

-import javax.sql.DataSource;

-

-import org.apache.commons.jcs.auxiliary.disk.jdbc.JDBCDiskCacheAttributes;

-

-

-/**

- * A factory that returns a DataSource.

- * Borrowed from Apache DB Torque

- *

- * @author <a href="mailto:jmcnally@apache.org">John McNally</a>

- * @author <a href="mailto:fischer@seitenbau.de">Thomas Fischer</a>

- * @version $Id: DataSourceFactory.java 1336091 2012-05-09 11:09:40Z tfischer $

- */

-public interface DataSourceFactory

-{

-    /**

-     * Key for the configuration which contains DataSourceFactories

-     */

-    String DSFACTORY_KEY = "dsfactory";

-

-    /**

-     *  Key for the configuration which contains the fully qualified name

-     *  of the factory implementation class

-     */

-    String FACTORY_KEY = "factory";

-

-    /**

-     * @return the name of the factory.

-     */

-    String getName();

-

-    /**

-     * @return the <code>DataSource</code> configured by the factory.

-     * @throws SQLException if the source can't be returned

-     */

-    DataSource getDataSource() throws SQLException;

-

-    /**

-     * Initialize the factory.

-     *

-     * @param config the factory settings

-     * @throws SQLException Any exceptions caught during processing will be

-     *         rethrown wrapped into a SQLException.

-     */

-    void initialize(JDBCDiskCacheAttributes config)

-        throws SQLException;

-

-    /**

-     * A hook which is called when the resources of the associated DataSource

-     * can be released.

-     * After close() is called, the other methods may not work any more

-     * (e.g. getDataSource() might return null).

-     * It is not guaranteed that this method does anything. For example,

-     * we do not want to close connections retrieved via JNDI, so the

-     * JndiDataSouurceFactory does not close these connections

-     *

-     * @throws SQLException Any exceptions caught during processing will be

-     *         rethrown wrapped into a SQLException.

-     */

-    void close()

-        throws SQLException;

-}

diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/dsfactory/JndiDataSourceFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/dsfactory/JndiDataSourceFactory.java
deleted file mode 100644
index ce84aef..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/dsfactory/JndiDataSourceFactory.java
+++ /dev/null
@@ -1,173 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.jdbc.dsfactory;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.sql.SQLException;
-import java.util.Hashtable;
-import java.util.Map;
-
-import javax.naming.Context;
-import javax.naming.InitialContext;
-import javax.naming.NamingException;
-import javax.sql.DataSource;
-
-import org.apache.commons.jcs.auxiliary.disk.jdbc.JDBCDiskCacheAttributes;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * A factory that looks up the DataSource from JNDI.  It is also able
- * to deploy the DataSource based on properties found in the
- * configuration.
- *
- * This factory tries to avoid excessive context lookups to improve speed.
- * The time between two lookups can be configured. The default is 0 (no cache).
- *
- * Borrowed and adapted from Apache DB Torque
- *
- * @author <a href="mailto:jmcnally@apache.org">John McNally</a>
- * @author <a href="mailto:thomas@vandahl.org">Thomas Vandahl</a>
- */
-public class JndiDataSourceFactory implements DataSourceFactory
-{
-    /** The log. */
-    private static Log log = LogManager.getLog(JndiDataSourceFactory.class);
-
-    /** The name of the factory. */
-    private String name;
-
-    /** The path to get the resource from. */
-    private String path;
-
-    /** The context to get the resource from. */
-    private Context ctx;
-
-    /** A locally cached copy of the DataSource */
-    private DataSource ds = null;
-
-    /** Time of last actual lookup action */
-    private long lastLookup = 0;
-
-    /** Time between two lookups */
-    private long ttl = 0; // ms
-
-    /**
-     * @return the name of the factory.
-     */
-    @Override
-	public String getName()
-    {
-    	return name;
-    }
-
-    /**
-     * @see org.apache.commons.jcs.auxiliary.disk.jdbc.dsfactory.DataSourceFactory#getDataSource()
-     */
-    @Override
-	public DataSource getDataSource() throws SQLException
-    {
-        long time = System.currentTimeMillis();
-
-        if (ds == null || time - lastLookup > ttl)
-        {
-            try
-            {
-                synchronized (ctx)
-                {
-                    ds = ((DataSource) ctx.lookup(path));
-                }
-                lastLookup = time;
-            }
-            catch (NamingException e)
-            {
-                throw new SQLException(e);
-            }
-        }
-
-        return ds;
-    }
-
-    /**
-     * @see org.apache.commons.jcs.auxiliary.disk.jdbc.dsfactory.DataSourceFactory#initialize(JDBCDiskCacheAttributes)
-     */
-    @Override
-	public void initialize(JDBCDiskCacheAttributes config) throws SQLException
-    {
-    	this.name = config.getConnectionPoolName();
-        initJNDI(config);
-    }
-
-    /**
-     * Initializes JNDI.
-     *
-     * @param config where to read the settings from
-     * @throws SQLException if a property set fails
-     */
-    private void initJNDI(JDBCDiskCacheAttributes config) throws SQLException
-    {
-        log.debug("Starting initJNDI");
-
-        try
-        {
-            this.path = config.getJndiPath();
-            log.debug("JNDI path: {0}", path);
-
-            this.ttl = config.getJndiTTL();
-            log.debug("Time between context lookups: {0}", ttl);
-
-    		Hashtable<String, Object> env = new Hashtable<>();
-            ctx = new InitialContext(env);
-
-            if (log.isTraceEnabled())
-            {
-            	log.trace("Created new InitialContext");
-            	debugCtx(ctx);
-            }
-        }
-        catch (NamingException e)
-        {
-            throw new SQLException(e);
-        }
-    }
-
-    /**
-     * Does nothing. We do not want to close a dataSource retrieved from Jndi,
-     * because other applications might use it as well.
-     */
-    @Override
-	public void close()
-    {
-        // do nothing
-    }
-
-    /**
-     *
-     * @param ctx the context
-     * @throws NamingException
-     */
-    private void debugCtx(Context ctx) throws NamingException
-    {
-        log.trace("InitialContext -------------------------------");
-        Map<?, ?> env = ctx.getEnvironment();
-        log.trace("Environment properties: {0}", env.size());
-        env.forEach((key, value) -> log.trace("    {0}: {1}", key, value));
-        log.trace("----------------------------------------------");
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/dsfactory/SharedPoolDataSourceFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/dsfactory/SharedPoolDataSourceFactory.java
deleted file mode 100644
index 1104f96..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/dsfactory/SharedPoolDataSourceFactory.java
+++ /dev/null
@@ -1,152 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.jdbc.dsfactory;

-

-/*

- * Licensed to the Apache Software Foundation (ASF) under one

- * or more contributor license agreements.  See the NOTICE file

- * distributed with this work for additional information

- * regarding copyright ownership.  The ASF licenses this file

- * to you under the Apache License, Version 2.0 (the

- * "License"); you may not use this file except in compliance

- * with the License.  You may obtain a copy of the License at

- *

- *   http://www.apache.org/licenses/LICENSE-2.0

- *

- * Unless required by applicable law or agreed to in writing,

- * software distributed under the License is distributed on an

- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

- * KIND, either express or implied.  See the License for the

- * specific language governing permissions and limitations

- * under the License.

- */

-

-import java.sql.SQLException;

-

-import javax.sql.ConnectionPoolDataSource;

-import javax.sql.DataSource;

-

-import org.apache.commons.dbcp2.cpdsadapter.DriverAdapterCPDS;

-import org.apache.commons.dbcp2.datasources.InstanceKeyDataSource;

-import org.apache.commons.dbcp2.datasources.SharedPoolDataSource;

-import org.apache.commons.jcs.auxiliary.disk.jdbc.JDBCDiskCacheAttributes;

-import org.apache.commons.jcs.log.Log;

-import org.apache.commons.jcs.log.LogManager;

-

-/**

- * A factory that looks up the DataSource using the JDBC2 pool methods.

- *

- * Borrowed and adapted from Apache DB Torque

- *

- * @author <a href="mailto:jmcnally@apache.org">John McNally</a>

- * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>

- */

-public class SharedPoolDataSourceFactory implements DataSourceFactory

-{

-    /** The log. */

-    private static Log log = LogManager.getLog(SharedPoolDataSourceFactory.class);

-

-    /** The name of the factory. */

-    private String name;

-

-    /** The wrapped <code>DataSource</code>. */

-    private SharedPoolDataSource ds = null;

-

-    /**

-     * @return the name of the factory.

-     */

-    @Override

-	public String getName()

-    {

-    	return name;

-    }

-

-    /**

-     * @see org.apache.commons.jcs.auxiliary.disk.jdbc.dsfactory.DataSourceFactory#getDataSource()

-     */

-    @Override

-    public DataSource getDataSource()

-    {

-        return ds;

-    }

-

-    /**

-     * @see org.apache.commons.jcs.auxiliary.disk.jdbc.dsfactory.DataSourceFactory#initialize(JDBCDiskCacheAttributes)

-     */

-    @Override

-	public void initialize(JDBCDiskCacheAttributes config) throws SQLException

-    {

-    	this.name = config.getConnectionPoolName();

-        ConnectionPoolDataSource cpds = initCPDS(config);

-        SharedPoolDataSource dataSource = new SharedPoolDataSource();

-        initJdbc2Pool(dataSource, config);

-        dataSource.setConnectionPoolDataSource(cpds);

-        dataSource.setMaxTotal(config.getMaxTotal());

-        this.ds = dataSource;

-    }

-

-    /**

-     * Closes the pool associated with this factory and releases it.

-     * @throws SQLException if the pool cannot be closed properly

-     */

-    @Override

-	public void close() throws SQLException

-    {

-        try

-        {

-            if (ds != null)

-            {

-                ds.close();

-            }

-        }

-        catch (Exception e)

-        {

-        	throw new SQLException("Exception caught closing data source", e);

-        }

-        ds = null;

-    }

-

-    /**

-     * Initializes the ConnectionPoolDataSource.

-     *

-     * @param config where to read the settings from

-     * @throws SQLException if a property set fails

-     * @return a configured <code>ConnectionPoolDataSource</code>

-     */

-    private ConnectionPoolDataSource initCPDS(final JDBCDiskCacheAttributes config)

-        throws SQLException

-    {

-        log.debug("Starting initCPDS");

-

-        DriverAdapterCPDS cpds = new DriverAdapterCPDS();

-

-        try

-        {

-			cpds.setDriver(config.getDriverClassName());

-		}

-        catch (ClassNotFoundException e)

-        {

-			throw new SQLException("Driver class not found " + config.getDriverClassName(), e);

-		}

-

-        cpds.setUrl(config.getUrl());

-        cpds.setUser(config.getUserName());

-        cpds.setPassword(config.getPassword());

-

-        return cpds;

-    }

-

-    /**

-     * Initializes the Jdbc2PoolDataSource.

-     *

-     * @param dataSource the dataSource to initialize, not null.

-     * @param config where to read the settings from, not null.

-     *

-     * @throws SQLException if a property set fails.

-     */

-    private void initJdbc2Pool(final InstanceKeyDataSource dataSource, final JDBCDiskCacheAttributes config)

-        throws SQLException

-    {

-        log.debug("Starting initJdbc2Pool");

-

-        dataSource.setDescription(config.getConnectionPoolName());

-    }

-}

diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/hsql/HSQLDiskCacheFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/hsql/HSQLDiskCacheFactory.java
deleted file mode 100644
index 8a50305..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/hsql/HSQLDiskCacheFactory.java
+++ /dev/null
@@ -1,129 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.jdbc.hsql;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.sql.Connection;
-import java.sql.DatabaseMetaData;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-
-import javax.sql.DataSource;
-
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes;
-import org.apache.commons.jcs.auxiliary.disk.jdbc.JDBCDiskCache;
-import org.apache.commons.jcs.auxiliary.disk.jdbc.JDBCDiskCacheAttributes;
-import org.apache.commons.jcs.auxiliary.disk.jdbc.JDBCDiskCacheFactory;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * This factory should create hsql disk caches.
- * <p>
- * @author Aaron Smuts
- */
-public class HSQLDiskCacheFactory
-    extends JDBCDiskCacheFactory
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog( HSQLDiskCacheFactory.class );
-
-    /**
-     * This factory method should create an instance of the hsqlcache.
-     * <p>
-     * @param rawAttr
-     * @param compositeCacheManager
-     * @param cacheEventLogger
-     * @param elementSerializer
-     * @return JDBCDiskCache
-     * @throws SQLException if the creation of the cache instance fails
-     */
-    @Override
-    public <K, V> JDBCDiskCache<K, V> createCache( AuxiliaryCacheAttributes rawAttr,
-			ICompositeCacheManager compositeCacheManager,
-			ICacheEventLogger cacheEventLogger,
-			IElementSerializer elementSerializer )
-			throws SQLException
-    {
-        // TODO get this from the attributes.
-        System.setProperty( "hsqldb.cache_scale", "8" );
-
-        JDBCDiskCache<K, V> cache = super.createCache(rawAttr, compositeCacheManager,
-                cacheEventLogger, elementSerializer);
-        setupDatabase( cache.getDataSource(), (JDBCDiskCacheAttributes) rawAttr );
-
-        return cache;
-    }
-
-    /**
-     * Creates the table if it doesn't exist
-     * <p>
-     * @param ds Data Source
-     * @param attributes Cache region configuration
-     * @throws SQLException
-     */
-    protected void setupDatabase( DataSource ds, JDBCDiskCacheAttributes attributes )
-        throws SQLException
-    {
-        try (Connection cConn = ds.getConnection())
-        {
-            setupTable( cConn, attributes.getTableName() );
-            log.info( "Finished setting up table [{0}]", attributes.getTableName());
-        }
-    }
-
-    /**
-     * SETUP TABLE FOR CACHE
-     * <p>
-     * @param cConn
-     * @param tableName
-     */
-    protected synchronized void setupTable( Connection cConn, String tableName ) throws SQLException
-    {
-        DatabaseMetaData dmd = cConn.getMetaData();
-        ResultSet result = dmd.getTables(null, null, tableName, null);
-
-        if (!result.next())
-        {
-            // TODO make the cached nature of the table configurable
-            StringBuilder createSql = new StringBuilder();
-            createSql.append( "CREATE CACHED TABLE ").append( tableName );
-            createSql.append( "( " );
-            createSql.append( "CACHE_KEY             VARCHAR(250)          NOT NULL, " );
-            createSql.append( "REGION                VARCHAR(250)          NOT NULL, " );
-            createSql.append( "ELEMENT               BINARY, " );
-            createSql.append( "CREATE_TIME           TIMESTAMP, " );
-            createSql.append( "UPDATE_TIME_SECONDS   BIGINT, " );
-            createSql.append( "MAX_LIFE_SECONDS      BIGINT, " );
-            createSql.append( "SYSTEM_EXPIRE_TIME_SECONDS      BIGINT, " );
-            createSql.append( "IS_ETERNAL            CHAR(1), " );
-            createSql.append( "PRIMARY KEY (CACHE_KEY, REGION) " );
-            createSql.append( ");" );
-
-            try (Statement sStatement = cConn.createStatement())
-            {
-                sStatement.execute( createSql.toString() );
-            }
-        }
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/mysql/MySQLDiskCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/mysql/MySQLDiskCache.java
deleted file mode 100644
index b1fe59a..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/mysql/MySQLDiskCache.java
+++ /dev/null
@@ -1,165 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.jdbc.mysql;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.sql.SQLException;
-import java.util.Map;
-
-import org.apache.commons.jcs.auxiliary.disk.jdbc.JDBCDiskCache;
-import org.apache.commons.jcs.auxiliary.disk.jdbc.TableState;
-import org.apache.commons.jcs.auxiliary.disk.jdbc.dsfactory.DataSourceFactory;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * The MySQLDiskCache extends the core JDBCDiskCache.
- * <p>
- * Although the generic JDBC Disk Cache can be used for MySQL, the MySQL JDBC Disk Cache has
- * additional features, such as table optimization that are particular to MySQL.
- * <p>
- * @author Aaron Smuts
- */
-public class MySQLDiskCache<K, V>
-	extends JDBCDiskCache<K, V>
-{
-    /** local logger */
-    private static final Log log = LogManager.getLog( MySQLDiskCache.class );
-
-    /** config attributes */
-    private final MySQLDiskCacheAttributes mySQLDiskCacheAttributes;
-
-    /**
-     * Delegates to the super and makes use of the MySQL specific parameters used for scheduled
-     * optimization.
-     * <p>
-     * @param attributes the configuration object for this cache
-     * @param dsFactory the DataSourceFactory for this cache
-     * @param tableState an object to track table operations
-     * @param compositeCacheManager the global cache manager
-     * @throws SQLException if the pool access could not be set up
-     */
-    public MySQLDiskCache( MySQLDiskCacheAttributes attributes, DataSourceFactory dsFactory,
-    		TableState tableState, ICompositeCacheManager compositeCacheManager ) throws SQLException
-    {
-        super( attributes, dsFactory, tableState, compositeCacheManager );
-
-        mySQLDiskCacheAttributes = attributes;
-
-        log.debug( "MySQLDiskCacheAttributes = {0}", attributes );
-    }
-
-    /**
-     * This delegates to the generic JDBC disk cache. If we are currently optimizing, then this
-     * method will balk and return null.
-     * <p>
-     * @param key Key to locate value for.
-     * @return An object matching key, or null.
-     */
-    @Override
-    protected ICacheElement<K, V> processGet( K key )
-    {
-        if ( this.getTableState().getState() == TableState.OPTIMIZATION_RUNNING )
-        {
-            if ( this.mySQLDiskCacheAttributes.isBalkDuringOptimization() )
-            {
-                return null;
-            }
-        }
-        return super.processGet( key );
-    }
-
-    /**
-     * This delegates to the generic JDBC disk cache. If we are currently optimizing, then this
-     * method will balk and return null.
-     * <p>
-     * @param pattern used for like query.
-     * @return An object matching key, or null.
-     */
-    @Override
-    protected Map<K, ICacheElement<K, V>> processGetMatching( String pattern )
-    {
-        if ( this.getTableState().getState() == TableState.OPTIMIZATION_RUNNING )
-        {
-            if ( this.mySQLDiskCacheAttributes.isBalkDuringOptimization() )
-            {
-                return null;
-            }
-        }
-        return super.processGetMatching( pattern );
-    }
-
-    /**
-     * @param pattern
-     * @return String to use in the like query.
-     */
-    @Override
-    public String constructLikeParameterFromPattern( String pattern )
-    {
-        String likePattern = pattern.replaceAll( "\\.\\+", "%" );
-        likePattern = likePattern.replaceAll( "\\.", "_" );
-
-        log.debug( "pattern = [{0}]", likePattern );
-
-        return likePattern;
-    }
-
-    /**
-     * This delegates to the generic JDBC disk cache. If we are currently optimizing, then this
-     * method will balk and do nothing.
-     * <p>
-     * @param element
-     */
-    @Override
-    protected void processUpdate( ICacheElement<K, V> element )
-    {
-        if ( this.getTableState().getState() == TableState.OPTIMIZATION_RUNNING )
-        {
-            if ( this.mySQLDiskCacheAttributes.isBalkDuringOptimization() )
-            {
-                return;
-            }
-        }
-        super.processUpdate( element );
-    }
-
-    /**
-     * Removed the expired. (now - create time) &gt; max life seconds * 1000
-     * <p>
-     * If we are currently optimizing, then this method will balk and do nothing.
-     * <p>
-     * TODO consider blocking and trying again.
-     * <p>
-     * @return the number deleted
-     */
-    @Override
-    protected int deleteExpired()
-    {
-        if ( this.getTableState().getState() == TableState.OPTIMIZATION_RUNNING )
-        {
-            if ( this.mySQLDiskCacheAttributes.isBalkDuringOptimization() )
-            {
-                return -1;
-            }
-        }
-        return super.deleteExpired();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/mysql/MySQLDiskCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/mysql/MySQLDiskCacheAttributes.java
deleted file mode 100644
index fca9702..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/mysql/MySQLDiskCacheAttributes.java
+++ /dev/null
@@ -1,107 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.jdbc.mysql;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.disk.jdbc.JDBCDiskCacheAttributes;
-
-/**
- * This has additional attributes that are particular to the MySQL disk cache.
- * <p>
- * @author Aaron Smuts
- */
-public class MySQLDiskCacheAttributes
-    extends JDBCDiskCacheAttributes
-{
-    /** Don't change. */
-    private static final long serialVersionUID = -6535808344813320061L;
-
-    /**
-     * For now this is a simple comma delimited list of HH:MM:SS times to optimize
-     * the table. If none is supplied, then no optimizations will be performed.
-     * <p>
-     * In the future we can add a chron like scheduling system. This is to meet
-     * a pressing current need.
-     * <p>
-     * 03:01,15:00 will cause the optimizer to run at 3 am and at 3 pm.
-     */
-    private String optimizationSchedule = null;
-
-    /**
-     * If true, we will balk, that is return null during optimization rather than block.
-     */
-    public static final boolean DEFAULT_BALK_DURING_OPTIMIZATION = true;
-
-    /**
-     * If true, we will balk, that is return null during optimization rather than block.
-     * <p>
-     * <a href="http://en.wikipedia.org/wiki/Balking_pattern">Balking</a>
-     */
-    private boolean balkDuringOptimization = DEFAULT_BALK_DURING_OPTIMIZATION;
-
-    /**
-     * @param optimizationSchedule The optimizationSchedule to set.
-     */
-    public void setOptimizationSchedule( String optimizationSchedule )
-    {
-        this.optimizationSchedule = optimizationSchedule;
-    }
-
-    /**
-     * @return Returns the optimizationSchedule.
-     */
-    public String getOptimizationSchedule()
-    {
-        return optimizationSchedule;
-    }
-
-    /**
-     * @param balkDuringOptimization The balkDuringOptimization to set.
-     */
-    public void setBalkDuringOptimization( boolean balkDuringOptimization )
-    {
-        this.balkDuringOptimization = balkDuringOptimization;
-    }
-
-    /**
-     * Should we return null while optimizing the table.
-     * <p>
-     * @return Returns the balkDuringOptimization.
-     */
-    public boolean isBalkDuringOptimization()
-    {
-        return balkDuringOptimization;
-    }
-
-    /**
-     * For debugging.
-     * <p>
-     * @return debug string
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder buf = new StringBuilder();
-        buf.append( "\nMySQLDiskCacheAttributes" );
-        buf.append( "\n OptimizationSchedule [" + getOptimizationSchedule() + "]" );
-        buf.append( "\n BalkDuringOptimization [" + isBalkDuringOptimization() + "]" );
-        buf.append( super.toString() );
-        return buf.toString();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/mysql/MySQLDiskCacheFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/mysql/MySQLDiskCacheFactory.java
deleted file mode 100644
index 728201c..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/mysql/MySQLDiskCacheFactory.java
+++ /dev/null
@@ -1,162 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.jdbc.mysql;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.sql.SQLException;
-import java.text.ParseException;
-import java.util.Date;
-import java.util.concurrent.TimeUnit;
-
-import javax.sql.DataSource;
-
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes;
-import org.apache.commons.jcs.auxiliary.disk.jdbc.JDBCDiskCacheFactory;
-import org.apache.commons.jcs.auxiliary.disk.jdbc.TableState;
-import org.apache.commons.jcs.auxiliary.disk.jdbc.dsfactory.DataSourceFactory;
-import org.apache.commons.jcs.auxiliary.disk.jdbc.mysql.util.ScheduleParser;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * This factory should create mysql disk caches.
- * <p>
- * @author Aaron Smuts
- */
-public class MySQLDiskCacheFactory
-    extends JDBCDiskCacheFactory
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog( MySQLDiskCacheFactory.class );
-
-    /**
-     * This factory method should create an instance of the mysqlcache.
-     * <p>
-     * @param rawAttr specific cache configuration attributes
-     * @param compositeCacheManager the global cache manager
-     * @param cacheEventLogger a specific logger for cache events
-     * @param elementSerializer a serializer for cache elements
-     * @return MySQLDiskCache the cache instance
-     * @throws SQLException if the cache instance could not be created
-     */
-    @Override
-    public <K, V> MySQLDiskCache<K, V> createCache( AuxiliaryCacheAttributes rawAttr,
-            ICompositeCacheManager compositeCacheManager,
-            ICacheEventLogger cacheEventLogger, IElementSerializer elementSerializer )
-            throws SQLException
-    {
-        MySQLDiskCacheAttributes cattr = (MySQLDiskCacheAttributes) rawAttr;
-        TableState tableState = getTableState( cattr.getTableName() );
-        DataSourceFactory dsFactory = getDataSourceFactory(cattr, compositeCacheManager.getConfigurationProperties());
-
-        MySQLDiskCache<K, V> cache = new MySQLDiskCache<>( cattr, dsFactory, tableState, compositeCacheManager );
-        cache.setCacheEventLogger( cacheEventLogger );
-        cache.setElementSerializer( elementSerializer );
-
-        // create a shrinker if we need it.
-        createShrinkerWhenNeeded( cattr, cache );
-        scheduleOptimizations( cattr, tableState, cache.getDataSource() );
-
-        return cache;
-
-    }
-
-    /**
-     * For each time in the optimization schedule, this calls schedule Optimization.
-     * <p>
-     * @param attributes configuration properties.
-     * @param tableState for noting optimization in progress, etc.
-     * @param ds the DataSource
-     */
-    protected void scheduleOptimizations( MySQLDiskCacheAttributes attributes, TableState tableState, DataSource ds  )
-    {
-        if ( attributes != null )
-        {
-            if ( attributes.getOptimizationSchedule() != null )
-            {
-                log.info( "Will try to configure optimization for table [{0}] on schedule [{1}]",
-                        () -> attributes.getTableName(),  () -> attributes.getOptimizationSchedule());
-
-                MySQLTableOptimizer optimizer = new MySQLTableOptimizer( attributes, tableState, ds );
-
-                // loop through the dates.
-                try
-                {
-                    Date[] dates = ScheduleParser.createDatesForSchedule( attributes.getOptimizationSchedule() );
-                    if ( dates != null )
-                    {
-                        for ( int i = 0; i < dates.length; i++ )
-                        {
-                            this.scheduleOptimization( dates[i], optimizer );
-                        }
-                    }
-                }
-                catch ( ParseException e )
-                {
-                    log.warn( "Problem creating optimization schedule for table [{0}]",
-                            attributes.getTableName(), e );
-                }
-            }
-            else
-            {
-                log.info( "Optimization is not configured for table [{0}]",
-                        attributes.getTableName());
-            }
-        }
-    }
-
-    /**
-     * This takes in a single time and schedules the optimizer to be called at that time every day.
-     * <p>
-     * @param startTime -- HH:MM:SS format
-     * @param optimizer
-     */
-    protected void scheduleOptimization( Date startTime, MySQLTableOptimizer optimizer )
-    {
-        log.info( "startTime [{0}] for optimizer {1}", startTime, optimizer );
-
-        Date now = new Date();
-        long initialDelay = startTime.getTime() - now.getTime();
-
-        // have the daemon execute the optimization
-        getScheduledExecutorService().scheduleAtFixedRate(() -> optimizeTable(optimizer),
-                initialDelay, 86400L, TimeUnit.SECONDS );
-    }
-
-    /**
-     * This calls the optimizers' optimize table method. This is used by the timer.
-     * <p>
-     * @author Aaron Smuts
-     */
-    private void optimizeTable(MySQLTableOptimizer optimizer)
-    {
-        if ( optimizer != null )
-        {
-            boolean success = optimizer.optimizeTable();
-            log.info( "Optimization success status [{0}]", success );
-        }
-        else
-        {
-            log.warn( "OptimizerRunner: The optimizer is null. Could not optimize table." );
-        }
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/mysql/MySQLTableOptimizer.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/mysql/MySQLTableOptimizer.java
deleted file mode 100644
index 585947f..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/mysql/MySQLTableOptimizer.java
+++ /dev/null
@@ -1,278 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.jdbc.mysql;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.sql.Connection;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-
-import javax.sql.DataSource;
-
-import org.apache.commons.jcs.auxiliary.disk.jdbc.TableState;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-import org.apache.commons.jcs.utils.timing.ElapsedTimer;
-
-/**
- * The MySQL Table Optimizer can optimize MySQL tables. It knows how to optimize for MySQL databases
- * in particular and how to repair the table if it is corrupted in the process.
- * <p>
- * We will probably be able to abstract out a generic optimizer interface from this class in the
- * future.
- * <p>
- * @author Aaron Smuts
- */
-public class MySQLTableOptimizer
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog( MySQLTableOptimizer.class );
-
-    /** The data source */
-    private DataSource dataSource = null;
-
-    /** The name of the table. */
-    private String tableName = null;
-
-    /** optimizing, etc. */
-    private TableState tableState;
-
-    /**
-     * This constructs an optimizer with the disk cacn properties.
-     * <p>
-     * @param attributes
-     * @param tableState We mark the table status as optimizing when this is happening.
-     * @param dataSource access to the database
-     */
-    public MySQLTableOptimizer( MySQLDiskCacheAttributes attributes, TableState tableState, DataSource dataSource )
-    {
-        setTableName( attributes.getTableName() );
-
-        this.tableState = tableState;
-        this.dataSource = dataSource;
-    }
-
-    /**
-     * A scheduler will call this method. When it is called the table state is marked as optimizing.
-     * TODO we need to verify that no deletions are running before we call optimize. We should wait
-     * if a deletion is in progress.
-     * <p>
-     * This restores when there is an optimization error. The error output looks like this:
-     *
-     * <pre>
-     *           mysql&gt; optimize table JCS_STORE_FLIGHT_OPTION_ITINERARY;
-     *               +---------------------------------------------+----------+----------+---------------------+
-     *               | Table                                       | Op       | Msg_type | Msg_text            |
-     *               +---------------------------------------------+----------+----------+---------------------+
-     *               | jcs_cache.JCS_STORE_FLIGHT_OPTION_ITINERARY | optimize | error    | 2 when fixing table |
-     *               | jcs_cache.JCS_STORE_FLIGHT_OPTION_ITINERARY | optimize | status   | Operation failed    |
-     *               +---------------------------------------------+----------+----------+---------------------+
-     *               2 rows in set (51.78 sec)
-     * </pre>
-     *
-     * A successful repair response looks like this:
-     *
-     * <pre>
-     *        mysql&gt; REPAIR TABLE JCS_STORE_FLIGHT_OPTION_ITINERARY;
-     *            +---------------------------------------------+--------+----------+----------------------------------------------+
-     *            | Table                                       | Op     | Msg_type | Msg_text                                     |
-     *            +---------------------------------------------+--------+----------+----------------------------------------------+
-     *            | jcs_cache.JCS_STORE_FLIGHT_OPTION_ITINERARY | repair | error    | 2 when fixing table                          |
-     *            | jcs_cache.JCS_STORE_FLIGHT_OPTION_ITINERARY | repair | warning  | Number of rows changed from 131276 to 260461 |
-     *            | jcs_cache.JCS_STORE_FLIGHT_OPTION_ITINERARY | repair | status   | OK                                           |
-     *            +---------------------------------------------+--------+----------+----------------------------------------------+
-     *            3 rows in set (3 min 5.94 sec)
-     * </pre>
-     *
-     * A successful optimization looks like this:
-     *
-     * <pre>
-     *       mysql&gt; optimize table JCS_STORE_DEFAULT;
-     *           +-----------------------------+----------+----------+----------+
-     *           | Table                       | Op       | Msg_type | Msg_text |
-     *           +-----------------------------+----------+----------+----------+
-     *           | jcs_cache.JCS_STORE_DEFAULT | optimize | status   | OK       |
-     *           +-----------------------------+----------+----------+----------+
-     *           1 row in set (1.10 sec)
-     * </pre>
-     * @return true if it worked
-     */
-    public boolean optimizeTable()
-    {
-        ElapsedTimer timer = new ElapsedTimer();
-        boolean success = false;
-
-        if ( tableState.getState() == TableState.OPTIMIZATION_RUNNING )
-        {
-            log.warn( "Skipping optimization. Optimize was called, but the "
-                    + "table state indicates that an optimization is currently running." );
-            return false;
-        }
-
-        try
-        {
-            tableState.setState( TableState.OPTIMIZATION_RUNNING );
-            log.info( "Optimizing table [{0}]", this.getTableName());
-
-            try (Connection con = dataSource.getConnection())
-            {
-                // TEST
-
-                try (Statement sStatement = con.createStatement())
-                {
-                    ResultSet rs = sStatement.executeQuery( "optimize table " + this.getTableName() );
-
-                    // first row is error, then status
-                    // if there is only one row in the result set, everything
-                    // should be fine.
-                    // This may be mysql version specific.
-                    if ( rs.next() )
-                    {
-                        String status = rs.getString( "Msg_type" );
-                        String message = rs.getString( "Msg_text" );
-
-                        log.info( "Message Type: {0}", status );
-                        log.info( "Message: {0}", message );
-
-                        if ( "error".equals( status ) )
-                        {
-                            log.warn( "Optimization was in error. Will attempt "
-                                    + "to repair the table. Message: {0}", message);
-
-                            // try to repair the table.
-                            success = repairTable( sStatement );
-                        }
-                        else
-                        {
-                            success = true;
-                        }
-                    }
-
-                    // log the table status
-                    String statusString = getTableStatus( sStatement );
-                    log.info( "Table status after optimizing table [{0}]: {1}",
-                            this.getTableName(), statusString );
-                }
-                catch ( SQLException e )
-                {
-                    log.error( "Problem optimizing table [{0}]",
-                            this.getTableName(), e );
-                    return false;
-                }
-            }
-            catch ( SQLException e )
-            {
-                log.error( "Problem getting connection.", e );
-            }
-        }
-        finally
-        {
-            tableState.setState( TableState.FREE );
-
-            log.info( "Optimization of table [{0}] took {1} ms.",
-                    () -> this.getTableName(), () -> timer.getElapsedTime() );
-        }
-
-        return success;
-    }
-
-    /**
-     * This calls show table status and returns the result as a String.
-     * <p>
-     * @param sStatement
-     * @return String
-     * @throws SQLException
-     */
-    protected String getTableStatus( Statement sStatement )
-        throws SQLException
-    {
-        ResultSet statusResultSet = sStatement.executeQuery( "show table status" );
-        StringBuilder statusString = new StringBuilder();
-        int numColumns = statusResultSet.getMetaData().getColumnCount();
-        while ( statusResultSet.next() )
-        {
-            statusString.append( "\n" );
-            for ( int i = 1; i <= numColumns; i++ )
-            {
-                statusString.append( statusResultSet.getMetaData().getColumnLabel( i ) + " ["
-                    + statusResultSet.getString( i ) + "]  |  " );
-            }
-        }
-        return statusString.toString();
-    }
-
-    /**
-     * This is called if the optimization is in error.
-     * <p>
-     * It looks for "OK" in response. If it find "OK" as a message in any result set row, it returns
-     * true. Otherwise we assume that the repair failed.
-     * <p>
-     * @param sStatement
-     * @return true if successful
-     * @throws SQLException
-     */
-    protected boolean repairTable( Statement sStatement )
-        throws SQLException
-    {
-        boolean success = false;
-
-        // if( message != null && message.indexOf( ) )
-        ResultSet repairResult = sStatement.executeQuery( "repair table " + this.getTableName() );
-        StringBuilder repairString = new StringBuilder();
-        int numColumns = repairResult.getMetaData().getColumnCount();
-        while ( repairResult.next() )
-        {
-            for ( int i = 1; i <= numColumns; i++ )
-            {
-                repairString.append( repairResult.getMetaData().getColumnLabel( i ) + " [" + repairResult.getString( i )
-                    + "]  |  " );
-            }
-
-            String message = repairResult.getString( "Msg_text" );
-            if ( "OK".equals( message ) )
-            {
-                success = true;
-            }
-        }
-        log.info("{0}", repairString);
-
-        if ( !success )
-        {
-            log.warn( "Failed to repair the table. {0}", repairString );
-        }
-        return success;
-    }
-
-    /**
-     * @param tableName The tableName to set.
-     */
-    public void setTableName( String tableName )
-    {
-        this.tableName = tableName;
-    }
-
-    /**
-     * @return Returns the tableName.
-     */
-    public String getTableName()
-    {
-        return tableName;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/mysql/util/ScheduleParser.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/mysql/util/ScheduleParser.java
deleted file mode 100644
index 4e973e2..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/jdbc/mysql/util/ScheduleParser.java
+++ /dev/null
@@ -1,96 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.jdbc.mysql.util;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.StringTokenizer;
-
-/**
- * Parses the very simple schedule format.
- * <p>
- * @author Aaron Smuts
- */
-public class ScheduleParser
-{
-    /**
-     * For each date time that is separated by a comma in the
-     * OptimizationSchedule, create a date and add it to an array of dates.
-     * <p>
-     * @param schedule
-     * @return Date[]
-     * @throws ParseException
-     */
-    public static Date[] createDatesForSchedule( String schedule )
-        throws ParseException
-    {
-        if ( schedule == null )
-        {
-            throw new ParseException( "Cannot create schedules for a null String.", 0 );
-        }
-
-        StringTokenizer toker = new StringTokenizer( schedule, "," );
-        Date[] dates = new Date[toker.countTokens()];
-        int cnt = 0;
-        while ( toker.hasMoreTokens() )
-        {
-            String time = toker.nextToken();
-            dates[cnt] = getDateForSchedule( time );
-            cnt++;
-        }
-        return dates;
-    }
-
-    /**
-     * For a single string it creates a date that is the next time this hh:mm:ss
-     * combo will be seen.
-     * <p>
-     * @param startTime
-     * @return Date
-     * @throws ParseException
-     */
-    public static Date getDateForSchedule( String startTime )
-        throws ParseException
-    {
-        if ( startTime == null )
-        {
-            throw new ParseException( "Cannot create date for a null String.", 0 );
-        }
-
-        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
-        Date date = sdf.parse(startTime);
-        Calendar cal = Calendar.getInstance();
-        // This will result in a date of 1/1/1970
-        cal.setTime(date);
-
-        Calendar now = Calendar.getInstance();
-        cal.set(now.get(Calendar.YEAR), now.get(Calendar.MONTH), now.get(Calendar.DAY_OF_MONTH));
-
-        // if the date is less than now, add a day.
-        if ( cal.before( now ) )
-        {
-            cal.add( Calendar.DAY_OF_MONTH, 1 );
-        }
-
-        return cal.getTime();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/LateralCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/LateralCache.java
deleted file mode 100644
index 44e7a5d..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/LateralCache.java
+++ /dev/null
@@ -1,417 +0,0 @@
-package org.apache.commons.jcs.auxiliary.lateral;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.commons.jcs.auxiliary.AbstractAuxiliaryCacheEventLogging;
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes;
-import org.apache.commons.jcs.auxiliary.lateral.behavior.ILateralCacheAttributes;
-import org.apache.commons.jcs.engine.CacheInfo;
-import org.apache.commons.jcs.engine.CacheStatus;
-import org.apache.commons.jcs.engine.ZombieCacheServiceNonLocal;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheServiceNonLocal;
-import org.apache.commons.jcs.engine.behavior.IZombie;
-import org.apache.commons.jcs.engine.stats.Stats;
-import org.apache.commons.jcs.engine.stats.behavior.IStats;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * Lateral distributor. Returns null on get by default. Net search not implemented.
- */
-public class LateralCache<K, V>
-    extends AbstractAuxiliaryCacheEventLogging<K, V>
-{
-    /** The logger. */
-    private static final Log log = LogManager.getLog( LateralCache.class );
-
-    /** generalize this, use another interface */
-    private final ILateralCacheAttributes lateralCacheAttributes;
-
-    /** The region name */
-    final String cacheName;
-
-    /** either http, socket.udp, or socket.tcp can set in config */
-    private ICacheServiceNonLocal<K, V> lateralCacheService;
-
-    /** Monitors the connection. */
-    private LateralCacheMonitor monitor;
-
-    /**
-     * Constructor for the LateralCache object
-     * <p>
-     * @param cattr
-     * @param lateral
-     * @param monitor
-     */
-    public LateralCache( ILateralCacheAttributes cattr, ICacheServiceNonLocal<K, V> lateral, LateralCacheMonitor monitor )
-    {
-        this.cacheName = cattr.getCacheName();
-        this.lateralCacheAttributes = cattr;
-        this.lateralCacheService = lateral;
-        this.monitor = monitor;
-    }
-
-    /**
-     * Constructor for the LateralCache object
-     * <p>
-     * @param cattr
-     */
-    public LateralCache( ILateralCacheAttributes cattr )
-    {
-        this.cacheName = cattr.getCacheName();
-        this.lateralCacheAttributes = cattr;
-    }
-
-    /**
-     * Update lateral.
-     * <p>
-     * @param ce
-     * @throws IOException
-     */
-    @Override
-    protected void processUpdate( ICacheElement<K, V> ce )
-        throws IOException
-    {
-        try
-        {
-            if (ce != null)
-            {
-                log.debug( "update: lateral = [{0}], CacheInfo.listenerId = {1}",
-                        lateralCacheService, CacheInfo.listenerId );
-                lateralCacheService.update( ce, CacheInfo.listenerId );
-            }
-        }
-        catch ( IOException ex )
-        {
-            handleException( ex, "Failed to put [" + ce.getKey() + "] to " + ce.getCacheName() + "@" + lateralCacheAttributes );
-        }
-    }
-
-    /**
-     * The performance costs are too great. It is not recommended that you enable lateral gets.
-     * <p>
-     * @param key
-     * @return ICacheElement&lt;K, V&gt; or null
-     * @throws IOException
-     */
-    @Override
-    protected ICacheElement<K, V> processGet( K key )
-        throws IOException
-    {
-        ICacheElement<K, V> obj = null;
-
-        if ( this.lateralCacheAttributes.getPutOnlyMode() )
-        {
-            return null;
-        }
-        try
-        {
-            obj = lateralCacheService.get( cacheName, key );
-        }
-        catch ( Exception e )
-        {
-            log.error( e );
-            handleException( e, "Failed to get [" + key + "] from " + lateralCacheAttributes.getCacheName() + "@" + lateralCacheAttributes );
-        }
-        return obj;
-    }
-
-    /**
-     * @param pattern
-     * @return A map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache for any of these keys
-     * @throws IOException
-     */
-    @Override
-    protected Map<K, ICacheElement<K, V>> processGetMatching( String pattern )
-        throws IOException
-    {
-        if ( this.lateralCacheAttributes.getPutOnlyMode() )
-        {
-            return Collections.emptyMap();
-        }
-        try
-        {
-            return lateralCacheService.getMatching( cacheName, pattern );
-        }
-        catch ( IOException e )
-        {
-            log.error( e );
-            handleException( e, "Failed to getMatching [" + pattern + "] from " + lateralCacheAttributes.getCacheName() + "@" + lateralCacheAttributes );
-            return Collections.emptyMap();
-        }
-    }
-
-    /**
-     * Return the keys in this cache.
-     * <p>
-     * @see org.apache.commons.jcs.auxiliary.AuxiliaryCache#getKeySet()
-     */
-    @Override
-    public Set<K> getKeySet() throws IOException
-    {
-        try
-        {
-            return lateralCacheService.getKeySet( cacheName );
-        }
-        catch ( IOException ex )
-        {
-            handleException( ex, "Failed to get key set from " + lateralCacheAttributes.getCacheName() + "@"
-                + lateralCacheAttributes );
-        }
-        return Collections.emptySet();
-    }
-
-    /**
-     * Synchronously remove from the remote cache; if failed, replace the remote handle with a
-     * zombie.
-     * <p>
-     * @param key
-     * @return false always
-     * @throws IOException
-     */
-    @Override
-    protected boolean processRemove( K key )
-        throws IOException
-    {
-        log.debug( "removing key: {0}", key );
-
-        try
-        {
-            lateralCacheService.remove( cacheName, key, CacheInfo.listenerId );
-        }
-        catch ( IOException ex )
-        {
-            handleException( ex, "Failed to remove " + key + " from " + lateralCacheAttributes.getCacheName() + "@" + lateralCacheAttributes );
-        }
-        return false;
-    }
-
-    /**
-     * Synchronously removeAll from the remote cache; if failed, replace the remote handle with a
-     * zombie.
-     * <p>
-     * @throws IOException
-     */
-    @Override
-    protected void processRemoveAll()
-        throws IOException
-    {
-        try
-        {
-            lateralCacheService.removeAll( cacheName, CacheInfo.listenerId );
-        }
-        catch ( IOException ex )
-        {
-            handleException( ex, "Failed to remove all from " + lateralCacheAttributes.getCacheName() + "@" + lateralCacheAttributes );
-        }
-    }
-
-    /**
-     * Synchronously dispose the cache. Not sure we want this.
-     * <p>
-     * @throws IOException
-     */
-    @Override
-    protected void processDispose()
-        throws IOException
-    {
-        log.debug( "Disposing of lateral cache" );
-
-        ///* HELP: This section did nothing but generate compilation warnings.
-        // TODO: may limit this functionality. It is dangerous.
-        // asmuts -- Added functionality to help with warnings. I'm not getting
-        // any.
-        try
-        {
-            lateralCacheService.dispose( this.lateralCacheAttributes.getCacheName() );
-            // Should remove connection
-        }
-        catch ( IOException ex )
-        {
-            log.error( "Couldn't dispose", ex );
-            handleException( ex, "Failed to dispose " + lateralCacheAttributes.getCacheName() );
-        }
-    }
-
-    /**
-     * Returns the cache status.
-     * <p>
-     * @return The status value
-     */
-    @Override
-    public CacheStatus getStatus()
-    {
-        return this.lateralCacheService instanceof IZombie ? CacheStatus.ERROR : CacheStatus.ALIVE;
-    }
-
-    /**
-     * Returns the current cache size.
-     * <p>
-     * @return The size value
-     */
-    @Override
-    public int getSize()
-    {
-        return 0;
-    }
-
-    /**
-     * Gets the cacheType attribute of the LateralCache object
-     * <p>
-     * @return The cacheType value
-     */
-    @Override
-    public CacheType getCacheType()
-    {
-        return CacheType.LATERAL_CACHE;
-    }
-
-    /**
-     * Gets the cacheName attribute of the LateralCache object
-     * <p>
-     * @return The cacheName value
-     */
-    @Override
-    public String getCacheName()
-    {
-        return cacheName;
-    }
-
-    /**
-     * Not yet sure what to do here.
-     * <p>
-     * @param ex
-     * @param msg
-     * @throws IOException
-     */
-    private void handleException( Exception ex, String msg )
-        throws IOException
-    {
-        log.error( "Disabling lateral cache due to error {0}", msg, ex );
-
-        lateralCacheService = new ZombieCacheServiceNonLocal<>( lateralCacheAttributes.getZombieQueueMaxSize() );
-        // may want to flush if region specifies
-        // Notify the cache monitor about the error, and kick off the recovery
-        // process.
-        monitor.notifyError();
-
-        // could stop the net search if it is built and try to reconnect?
-        if ( ex instanceof IOException )
-        {
-            throw (IOException) ex;
-        }
-        throw new IOException( ex.getMessage() );
-    }
-
-    /**
-     * Replaces the current remote cache service handle with the given handle.
-     * <p>
-     * @param restoredLateral
-     */
-    public void fixCache( ICacheServiceNonLocal<K, V> restoredLateral )
-    {
-        if ( this.lateralCacheService != null && this.lateralCacheService instanceof ZombieCacheServiceNonLocal )
-        {
-            ZombieCacheServiceNonLocal<K, V> zombie = (ZombieCacheServiceNonLocal<K, V>) this.lateralCacheService;
-            this.lateralCacheService = restoredLateral;
-            try
-            {
-                zombie.propagateEvents( restoredLateral );
-            }
-            catch ( Exception e )
-            {
-                try
-                {
-                    handleException( e, "Problem propagating events from Zombie Queue to new Lateral Service." );
-                }
-                catch ( IOException e1 )
-                {
-                    // swallow, since this is just expected kick back.  Handle always throws
-                }
-            }
-        }
-        else
-        {
-            this.lateralCacheService = restoredLateral;
-        }
-    }
-
-    /**
-     * getStats
-     * <p>
-     * @return String
-     */
-    @Override
-    public String getStats()
-    {
-        return "";
-    }
-
-    /**
-     * @return Returns the AuxiliaryCacheAttributes.
-     */
-    @Override
-    public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
-    {
-        return lateralCacheAttributes;
-    }
-
-    /**
-     * @return debugging data.
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder buf = new StringBuilder();
-        buf.append( "\n LateralCache " );
-        buf.append( "\n Cache Name [" + lateralCacheAttributes.getCacheName() + "]" );
-        buf.append( "\n cattr =  [" + lateralCacheAttributes + "]" );
-        return buf.toString();
-    }
-
-    /**
-     * @return extra data.
-     */
-    @Override
-    public String getEventLoggingExtraInfo()
-    {
-        return null;
-    }
-
-    /**
-     * The NoWait on top does not call out to here yet.
-     * <p>
-     * @return almost nothing
-     */
-    @Override
-    public IStats getStatistics()
-    {
-        IStats stats = new Stats();
-        stats.setTypeName( "LateralCache" );
-        return stats;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/LateralCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/LateralCacheAttributes.java
deleted file mode 100644
index fba67f3..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/LateralCacheAttributes.java
+++ /dev/null
@@ -1,292 +0,0 @@
-package org.apache.commons.jcs.auxiliary.lateral;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.AbstractAuxiliaryCacheAttributes;
-import org.apache.commons.jcs.auxiliary.lateral.behavior.ILateralCacheAttributes;
-
-/**
- * This class stores attributes for all of the available lateral cache auxiliaries.
- */
-public class LateralCacheAttributes
-    extends AbstractAuxiliaryCacheAttributes
-    implements ILateralCacheAttributes
-{
-    /** Don't change */
-    private static final long serialVersionUID = -3408449508837393660L;
-
-    /** Default receive setting */
-    private static final boolean DEFAULT_RECEIVE = true;
-
-    /** THe type of lateral */
-    private String transmissionTypeName = "UDP";
-
-    /** indicates the lateral type, this needs to change */
-    private Type transmissionType = Type.UDP;
-
-    /** The http servers */
-    private String httpServers;
-
-    /** used to identify the service that this manager will be operating on */
-    private String httpServer = "";
-
-    /** this needs to change */
-    private String udpMulticastAddr = "228.5.6.7";
-
-    /** this needs to change */
-    private int udpMulticastPort = 6789;
-
-    /** this needs to change */
-    private int httpListenerPort = 8080;
-
-    /** disables gets from laterals */
-    private boolean putOnlyMode = true;
-
-    /**
-     * do we receive and broadcast or only broadcast this is useful when you don't want to get any
-     * notifications
-     */
-    private boolean receive = DEFAULT_RECEIVE;
-
-    /** If the primary fails, we will queue items before reconnect.  This limits the number of items that can be queued. */
-    private int zombieQueueMaxSize = DEFAULT_ZOMBIE_QUEUE_MAX_SIZE;
-
-    /**
-     * Sets the httpServer attribute of the LateralCacheAttributes object
-     * <P>
-     * @param val The new httpServer value
-     */
-    @Override
-    public void setHttpServer( String val )
-    {
-        httpServer = val;
-    }
-
-    /**
-     * Gets the httpServer attribute of the LateralCacheAttributes object
-     * @return The httpServer value
-     */
-    @Override
-    public String getHttpServer()
-    {
-        return httpServer;
-    }
-
-    /**
-     * Sets the httpServers attribute of the LateralCacheAttributes object
-     * @param val The new httpServers value
-     */
-    @Override
-    public void setHttpServers( String val )
-    {
-        httpServers = val;
-    }
-
-    /**
-     * Gets the httpSrvers attribute of the LateralCacheAttributes object
-     * @return The httpServers value
-     */
-    @Override
-    public String getHttpServers()
-    {
-        return httpServers;
-    }
-
-    /**
-     * Sets the httpListenerPort attribute of the ILateralCacheAttributes object
-     * @param val The new tcpListenerPort value
-     */
-    @Override
-    public void setHttpListenerPort( int val )
-    {
-        this.httpListenerPort = val;
-    }
-
-    /**
-     * Gets the httpListenerPort attribute of the ILateralCacheAttributes object
-     * @return The httpListenerPort value
-     */
-    @Override
-    public int getHttpListenerPort()
-    {
-        return this.httpListenerPort;
-    }
-
-    /**
-     * Sets the udpMulticastAddr attribute of the LateralCacheAttributes object
-     * @param val The new udpMulticastAddr value
-     */
-    @Override
-    public void setUdpMulticastAddr( String val )
-    {
-        udpMulticastAddr = val;
-    }
-
-    /**
-     * Gets the udpMulticastAddr attribute of the LateralCacheAttributes object
-     * @return The udpMulticastAddr value
-     */
-    @Override
-    public String getUdpMulticastAddr()
-    {
-        return udpMulticastAddr;
-    }
-
-    /**
-     * Sets the udpMulticastPort attribute of the LateralCacheAttributes object
-     * @param val The new udpMulticastPort value
-     */
-    @Override
-    public void setUdpMulticastPort( int val )
-    {
-        udpMulticastPort = val;
-    }
-
-    /**
-     * Gets the udpMulticastPort attribute of the LateralCacheAttributes object
-     * @return The udpMulticastPort value
-     */
-    @Override
-    public int getUdpMulticastPort()
-    {
-        return udpMulticastPort;
-    }
-
-    /**
-     * Sets the transmissionType attribute of the LateralCacheAttributes object
-     * @param val The new transmissionType value
-     */
-    @Override
-    public void setTransmissionType( Type val )
-    {
-        this.transmissionType = val;
-        this.transmissionTypeName = val.toString();
-    }
-
-    /**
-     * Gets the transmissionType attribute of the LateralCacheAttributes object
-     * @return The transmissionType value
-     */
-    @Override
-    public Type getTransmissionType()
-    {
-        return this.transmissionType;
-    }
-
-    /**
-     * Sets the transmissionTypeName attribute of the LateralCacheAttributes object
-     * @param val The new transmissionTypeName value
-     */
-    @Override
-    public void setTransmissionTypeName( String val )
-    {
-        this.transmissionTypeName = val;
-        this.transmissionType = Type.valueOf(val);
-    }
-
-    /**
-     * Gets the transmissionTypeName attribute of the LateralCacheAttributes object
-     * @return The transmissionTypeName value
-     */
-    @Override
-    public String getTransmissionTypeName()
-    {
-        return this.transmissionTypeName;
-    }
-
-    /**
-     * Sets the outgoingOnlyMode attribute of the ILateralCacheAttributes. When this is true the
-     * lateral cache will only issue put and remove order and will not try to retrieve elements from
-     * other lateral caches.
-     * @param val The new transmissionTypeName value
-     */
-    @Override
-    public void setPutOnlyMode( boolean val )
-    {
-        this.putOnlyMode = val;
-    }
-
-    /**
-     * @return The outgoingOnlyMode value. Stops gets from going remote.
-     */
-    @Override
-    public boolean getPutOnlyMode()
-    {
-        return putOnlyMode;
-    }
-
-    /**
-     * @param receive The receive to set.
-     */
-    @Override
-    public void setReceive( boolean receive )
-    {
-        this.receive = receive;
-    }
-
-    /**
-     * @return Returns the receive.
-     */
-    @Override
-    public boolean isReceive()
-    {
-        return receive;
-    }
-
-    /**
-     * The number of elements the zombie queue will hold. This queue is used to store events if we
-     * loose our connection with the server.
-     * <p>
-     * @param zombieQueueMaxSize The zombieQueueMaxSize to set.
-     */
-    @Override
-    public void setZombieQueueMaxSize( int zombieQueueMaxSize )
-    {
-        this.zombieQueueMaxSize = zombieQueueMaxSize;
-    }
-
-    /**
-     * The number of elements the zombie queue will hold. This queue is used to store events if we
-     * loose our connection with the server.
-     * <p>
-     * @return Returns the zombieQueueMaxSize.
-     */
-    @Override
-    public int getZombieQueueMaxSize()
-    {
-        return zombieQueueMaxSize;
-    }
-
-    /**
-     * @return debug string.
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder buf = new StringBuilder();
-        //buf.append( "cacheName=" + cacheName + "\n" );
-        //buf.append( "putOnlyMode=" + putOnlyMode + "\n" );
-        //buf.append( "transmissionTypeName=" + transmissionTypeName + "\n" );
-        //buf.append( "transmissionType=" + transmissionType + "\n" );
-        //buf.append( "tcpServer=" + tcpServer + "\n" );
-        buf.append( transmissionTypeName + httpServer + udpMulticastAddr + String.valueOf( udpMulticastPort ) );
-        return buf.toString();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/LateralCacheMonitor.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/LateralCacheMonitor.java
deleted file mode 100644
index c78916b..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/LateralCacheMonitor.java
+++ /dev/null
@@ -1,136 +0,0 @@
-package org.apache.commons.jcs.auxiliary.lateral;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.apache.commons.jcs.auxiliary.AbstractAuxiliaryCacheMonitor;
-import org.apache.commons.jcs.auxiliary.lateral.socket.tcp.LateralTCPCacheFactory;
-import org.apache.commons.jcs.auxiliary.lateral.socket.tcp.behavior.ITCPLateralCacheAttributes;
-import org.apache.commons.jcs.engine.CacheStatus;
-import org.apache.commons.jcs.engine.ZombieCacheServiceNonLocal;
-import org.apache.commons.jcs.engine.behavior.ICacheServiceNonLocal;
-
-/**
- * Used to monitor and repair any failed connection for the lateral cache service. By default the
- * monitor operates in a failure driven mode. That is, it goes into a wait state until there is an
- * error. Upon the notification of a connection error, the monitor changes to operate in a time
- * driven mode. That is, it attempts to recover the connections on a periodic basis. When all failed
- * connections are restored, it changes back to the failure driven mode.
- */
-public class LateralCacheMonitor extends AbstractAuxiliaryCacheMonitor
-{
-    /**
-     * Map of caches to monitor
-     */
-    private ConcurrentHashMap<String, LateralCacheNoWait<?, ?>> caches;
-
-    /**
-     * Reference to the factory
-     */
-    private LateralTCPCacheFactory factory;
-
-    /**
-     * Allows close classes, ie testers to set the idle period to something testable.
-     * <p>
-     * @param idlePeriod
-     */
-    protected static void forceShortIdlePeriod( long idlePeriod )
-    {
-        LateralCacheMonitor.idlePeriod = idlePeriod;
-    }
-
-    /**
-     * Constructor for the LateralCacheMonitor object
-     * <p>
-     * It's the clients responsibility to decide how many of these there will be.
-     *
-     * @param factory a reference to the factory that manages the service instances
-     */
-    public LateralCacheMonitor(LateralTCPCacheFactory factory)
-    {
-        super("JCS-LateralCacheMonitor");
-        this.factory = factory;
-        this.caches = new ConcurrentHashMap<>();
-        setIdlePeriod(20000L);
-    }
-
-    /**
-     * Add a cache to be monitored
-     *
-     * @param cache the cache
-     */
-    public void addCache(LateralCacheNoWait<?, ?> cache)
-    {
-        this.caches.put(cache.getCacheName(), cache);
-
-        // if not yet started, go ahead
-        if (this.getState() == Thread.State.NEW)
-        {
-            this.start();
-        }
-    }
-
-    /**
-     * Clean up all resources before shutdown
-     */
-    @Override
-    public void dispose()
-    {
-        this.caches.clear();
-    }
-
-    /**
-     * Main processing method for the LateralCacheMonitor object
-     */
-    @Override
-    public void doWork()
-    {
-        // Monitor each cache instance one after the other.
-        log.info( "Number of caches to monitor = " + caches.size() );
-        //for
-        for (Map.Entry<String, LateralCacheNoWait<?, ?>> entry : caches.entrySet())
-        {
-            String cacheName = entry.getKey();
-
-            @SuppressWarnings("unchecked") // Downcast to match service
-            LateralCacheNoWait<Object, Object> c = (LateralCacheNoWait<Object, Object>) entry.getValue();
-            if ( c.getStatus() == CacheStatus.ERROR )
-            {
-                log.info( "Found LateralCacheNoWait in error, " + cacheName );
-
-                ITCPLateralCacheAttributes lca = (ITCPLateralCacheAttributes)c.getAuxiliaryCacheAttributes();
-
-                // Get service instance
-                ICacheServiceNonLocal<Object, Object> cacheService = factory.getCSNLInstance(lca);
-
-                // If we can't fix them, just skip and re-try in the
-                // next round.
-                if (cacheService instanceof ZombieCacheServiceNonLocal)
-                {
-                    continue;
-                }
-
-                c.fixCache(cacheService);
-            }
-        }
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/LateralCacheNoWait.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/LateralCacheNoWait.java
deleted file mode 100644
index 3ac2bfd..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/LateralCacheNoWait.java
+++ /dev/null
@@ -1,433 +0,0 @@
-package org.apache.commons.jcs.auxiliary.lateral;
-
-import java.io.IOException;
-import java.rmi.UnmarshalException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.AbstractAuxiliaryCache;
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes;
-import org.apache.commons.jcs.engine.CacheAdaptor;
-import org.apache.commons.jcs.engine.CacheEventQueueFactory;
-import org.apache.commons.jcs.engine.CacheInfo;
-import org.apache.commons.jcs.engine.CacheStatus;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheEventQueue;
-import org.apache.commons.jcs.engine.behavior.ICacheServiceNonLocal;
-import org.apache.commons.jcs.engine.stats.StatElement;
-import org.apache.commons.jcs.engine.stats.Stats;
-import org.apache.commons.jcs.engine.stats.behavior.IStatElement;
-import org.apache.commons.jcs.engine.stats.behavior.IStats;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * Used to queue up update requests to the underlying cache. These requests will be processed in
- * their order of arrival via the cache event queue processor.
- */
-public class LateralCacheNoWait<K, V>
-    extends AbstractAuxiliaryCache<K, V>
-{
-    /** The logger. */
-    private static final Log log = LogManager.getLog( LateralCacheNoWait.class );
-
-    /** The cache */
-    private final LateralCache<K, V> cache;
-
-    /** The event queue */
-    private ICacheEventQueue<K, V> eventQueue;
-
-    /** times get called */
-    private int getCount = 0;
-
-    /** times remove called */
-    private int removeCount = 0;
-
-    /** times put called */
-    private int putCount = 0;
-
-    /**
-     * Constructs with the given lateral cache, and fires up an event queue for asynchronous
-     * processing.
-     * <p>
-     * @param cache
-     */
-    public LateralCacheNoWait( LateralCache<K, V> cache )
-    {
-        this.cache = cache;
-
-        log.debug( "Constructing LateralCacheNoWait, LateralCache = [{0}]", cache );
-
-        CacheEventQueueFactory<K, V> fact = new CacheEventQueueFactory<>();
-        this.eventQueue = fact.createCacheEventQueue( new CacheAdaptor<>( cache ), CacheInfo.listenerId, cache
-            .getCacheName(), cache.getAuxiliaryCacheAttributes().getEventQueuePoolName(), cache
-            .getAuxiliaryCacheAttributes().getEventQueueType() );
-
-        // need each no wait to handle each of its real updates and removes,
-        // since there may
-        // be more than one per cache? alternative is to have the cache
-        // perform updates using a different method that specifies the listener
-        // this.q = new CacheEventQueue(new CacheAdaptor(this),
-        // LateralCacheInfo.listenerId, cache.getCacheName());
-        if ( cache.getStatus() == CacheStatus.ERROR )
-        {
-            eventQueue.destroy();
-        }
-    }
-
-    /**
-     * @param ce
-     * @throws IOException
-     */
-    @Override
-    public void update( ICacheElement<K, V> ce )
-        throws IOException
-    {
-        putCount++;
-        try
-        {
-            eventQueue.addPutEvent( ce );
-        }
-        catch ( IOException ex )
-        {
-            log.error( ex );
-            eventQueue.destroy();
-        }
-    }
-
-    /**
-     * Synchronously reads from the lateral cache.
-     * <p>
-     * @param key
-     * @return ICacheElement&lt;K, V&gt; if found, else null
-     */
-    @Override
-    public ICacheElement<K, V> get( K key )
-    {
-        getCount++;
-        if ( this.getStatus() != CacheStatus.ERROR )
-        {
-            try
-            {
-                return cache.get( key );
-            }
-            catch ( UnmarshalException ue )
-            {
-                log.debug( "Retrying the get owing to UnmarshalException..." );
-                try
-                {
-                    return cache.get( key );
-                }
-                catch ( IOException ex )
-                {
-                    log.error( "Failed in retrying the get for the second time." );
-                    eventQueue.destroy();
-                }
-            }
-            catch ( IOException ex )
-            {
-                eventQueue.destroy();
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Gets multiple items from the cache based on the given set of keys.
-     * <p>
-     * @param keys
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache for any of these keys
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMultiple(Set<K> keys)
-    {
-        if ( keys != null && !keys.isEmpty() )
-        {
-            Map<K, ICacheElement<K, V>> elements = keys.stream()
-                .collect(Collectors.toMap(
-                        key -> key,
-                        key -> get(key))).entrySet().stream()
-                    .filter(entry -> entry.getValue() != null)
-                    .collect(Collectors.toMap(
-                            entry -> entry.getKey(),
-                            entry -> entry.getValue()));
-
-            return elements;
-        }
-
-        return new HashMap<>();
-    }
-
-    /**
-     * Synchronously reads from the lateral cache.
-     * <p>
-     * @param pattern
-     * @return ICacheElement&lt;K, V&gt; if found, else empty
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMatching(String pattern)
-    {
-        getCount++;
-        if ( this.getStatus() != CacheStatus.ERROR )
-        {
-            try
-            {
-                return cache.getMatching( pattern );
-            }
-            catch ( UnmarshalException ue )
-            {
-                log.debug( "Retrying the get owing to UnmarshalException." );
-                try
-                {
-                    return cache.getMatching( pattern );
-                }
-                catch ( IOException ex )
-                {
-                    log.error( "Failed in retrying the get for the second time." );
-                    eventQueue.destroy();
-                }
-            }
-            catch ( IOException ex )
-            {
-                eventQueue.destroy();
-            }
-        }
-        return Collections.emptyMap();
-    }
-
-    /**
-     * Return the keys in this cache.
-     * <p>
-     * @see org.apache.commons.jcs.auxiliary.AuxiliaryCache#getKeySet()
-     */
-    @Override
-    public Set<K> getKeySet() throws IOException
-    {
-        try
-        {
-            return cache.getKeySet();
-        }
-        catch ( IOException ex )
-        {
-            log.error( ex );
-            eventQueue.destroy();
-        }
-        return Collections.emptySet();
-    }
-
-    /**
-     * Adds a remove request to the lateral cache.
-     * <p>
-     * @param key
-     * @return always false
-     */
-    @Override
-    public boolean remove( K key )
-    {
-        removeCount++;
-        try
-        {
-            eventQueue.addRemoveEvent( key );
-        }
-        catch ( IOException ex )
-        {
-            log.error( ex );
-            eventQueue.destroy();
-        }
-        return false;
-    }
-
-    /** Adds a removeAll request to the lateral cache. */
-    @Override
-    public void removeAll()
-    {
-        try
-        {
-            eventQueue.addRemoveAllEvent();
-        }
-        catch ( IOException ex )
-        {
-            log.error( ex );
-            eventQueue.destroy();
-        }
-    }
-
-    /** Adds a dispose request to the lateral cache. */
-    @Override
-    public void dispose()
-    {
-        try
-        {
-            eventQueue.addDisposeEvent();
-        }
-        catch ( IOException ex )
-        {
-            log.error( ex );
-            eventQueue.destroy();
-        }
-    }
-
-    /**
-     * No lateral invocation.
-     * <p>
-     * @return The size value
-     */
-    @Override
-    public int getSize()
-    {
-        return cache.getSize();
-    }
-
-    /**
-     * No lateral invocation.
-     * <p>
-     * @return The cacheType value
-     */
-    @Override
-    public CacheType getCacheType()
-    {
-        return cache.getCacheType();
-    }
-
-    /**
-     * Returns the asyn cache status. An error status indicates either the lateral connection is not
-     * available, or the asyn queue has been unexpectedly destroyed. No lateral invocation.
-     * <p>
-     * @return The status value
-     */
-    @Override
-    public CacheStatus getStatus()
-    {
-        return eventQueue.isWorking() ? cache.getStatus() : CacheStatus.ERROR;
-    }
-
-    /**
-     * Gets the cacheName attribute of the LateralCacheNoWait object
-     * <p>
-     * @return The cacheName value
-     */
-    @Override
-    public String getCacheName()
-    {
-        return cache.getCacheName();
-    }
-
-    /**
-     * Replaces the lateral cache service handle with the given handle and reset the queue by
-     * starting up a new instance.
-     * <p>
-     * @param lateral
-     */
-    public void fixCache( ICacheServiceNonLocal<K, V> lateral )
-    {
-        cache.fixCache( lateral );
-        resetEventQ();
-    }
-
-    /**
-     * Resets the event q by first destroying the existing one and starting up new one.
-     */
-    public void resetEventQ()
-    {
-        if ( eventQueue.isWorking() )
-        {
-            eventQueue.destroy();
-        }
-        CacheEventQueueFactory<K, V> fact = new CacheEventQueueFactory<>();
-        this.eventQueue = fact.createCacheEventQueue( new CacheAdaptor<>( cache ), CacheInfo.listenerId, cache
-            .getCacheName(), cache.getAuxiliaryCacheAttributes().getEventQueuePoolName(), cache
-            .getAuxiliaryCacheAttributes().getEventQueueType() );
-    }
-
-    /**
-     * @return Returns the AuxiliaryCacheAttributes.
-     */
-    @Override
-    public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
-    {
-        return cache.getAuxiliaryCacheAttributes();
-    }
-
-    /**
-     * getStats
-     * @return String
-     */
-    @Override
-    public String getStats()
-    {
-        return getStatistics().toString();
-    }
-
-    /**
-     * this won't be called since we don't do ICache logging here.
-     * <p>
-     * @return String
-     */
-    @Override
-    public String getEventLoggingExtraInfo()
-    {
-        return "Lateral Cache No Wait";
-    }
-
-    /**
-     * @return statistics about this communication
-     */
-    @Override
-    public IStats getStatistics()
-    {
-        IStats stats = new Stats();
-        stats.setTypeName( "Lateral Cache No Wait" );
-
-        ArrayList<IStatElement<?>> elems = new ArrayList<>();
-
-        // get the stats from the event queue too
-        IStats eqStats = this.eventQueue.getStatistics();
-        elems.addAll(eqStats.getStatElements());
-
-        elems.add(new StatElement<>( "Get Count", Integer.valueOf(this.getCount) ) );
-        elems.add(new StatElement<>( "Remove Count", Integer.valueOf(this.removeCount) ) );
-        elems.add(new StatElement<>( "Put Count", Integer.valueOf(this.putCount) ) );
-        elems.add(new StatElement<>( "Attributes", cache.getAuxiliaryCacheAttributes() ) );
-
-        stats.setStatElements( elems );
-
-        return stats;
-    }
-
-    /**
-     * @return debugging info.
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder buf = new StringBuilder();
-        buf.append( " LateralCacheNoWait " );
-        buf.append( " Status = " + this.getStatus() );
-        buf.append( " cache = [" + cache.toString() + "]" );
-        return buf.toString();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/LateralCacheNoWaitFacade.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/LateralCacheNoWaitFacade.java
deleted file mode 100644
index 4d15a3e..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/LateralCacheNoWaitFacade.java
+++ /dev/null
@@ -1,467 +0,0 @@
-package org.apache.commons.jcs.auxiliary.lateral;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.AbstractAuxiliaryCache;
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes;
-import org.apache.commons.jcs.auxiliary.lateral.behavior.ILateralCacheAttributes;
-import org.apache.commons.jcs.auxiliary.lateral.behavior.ILateralCacheListener;
-import org.apache.commons.jcs.engine.CacheStatus;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.stats.StatElement;
-import org.apache.commons.jcs.engine.stats.Stats;
-import org.apache.commons.jcs.engine.stats.behavior.IStatElement;
-import org.apache.commons.jcs.engine.stats.behavior.IStats;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * Used to provide access to multiple services under nowait protection. Composite factory should
- * construct LateralCacheNoWaitFacade to give to the composite cache out of caches it constructs
- * from the varies manager to lateral services. Perhaps the lateralcache factory should be able to
- * do this.
- */
-public class LateralCacheNoWaitFacade<K, V>
-    extends AbstractAuxiliaryCache<K, V>
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog( LateralCacheNoWaitFacade.class );
-
-    /** The queuing facade to the client. */
-    public LateralCacheNoWait<K, V>[] noWaits;
-
-    /** The region name */
-    private final String cacheName;
-
-    /** A cache listener */
-    private ILateralCacheListener<K, V> listener;
-
-    /** User configurable attributes. */
-    private final ILateralCacheAttributes lateralCacheAttributes;
-
-    /** Disposed state of this facade */
-    private boolean disposed = false;
-
-    /**
-     * Constructs with the given lateral cache, and fires events to any listeners.
-     * <p>
-     * @param noWaits
-     * @param cattr
-     */
-    public LateralCacheNoWaitFacade(ILateralCacheListener<K, V> listener, LateralCacheNoWait<K, V>[] noWaits, ILateralCacheAttributes cattr )
-    {
-        log.debug( "CONSTRUCTING NO WAIT FACADE" );
-        this.listener = listener;
-        this.noWaits = noWaits;
-        this.cacheName = cattr.getCacheName();
-        this.lateralCacheAttributes = cattr;
-    }
-
-    /**
-     * Tells you if the no wait is in the list or not.
-     * <p>
-     * @param noWait
-     * @return true if the noWait is in the list.
-     */
-    public boolean containsNoWait( LateralCacheNoWait<K, V> noWait )
-    {
-        Optional<LateralCacheNoWait<K, V>> optional = Arrays.stream(noWaits)
-                // we know noWait isn't null
-                .filter(nw -> noWait.equals( nw ))
-                .findFirst();
-
-        return optional.isPresent();
-    }
-
-    /**
-     * Adds a no wait to the list if it isn't already in the list.
-     * <p>
-     * @param noWait
-     * @return true if it wasn't already contained
-     */
-    public synchronized boolean addNoWait( LateralCacheNoWait<K, V> noWait )
-    {
-        if ( noWait == null )
-        {
-            return false;
-        }
-
-        if ( containsNoWait( noWait ) )
-        {
-            log.debug( "No Wait already contained, [{0}]", noWait );
-            return false;
-        }
-
-        @SuppressWarnings("unchecked") // No generic arrays in java
-        LateralCacheNoWait<K, V>[] newArray = new LateralCacheNoWait[noWaits.length + 1];
-
-        System.arraycopy( noWaits, 0, newArray, 0, noWaits.length );
-
-        // set the last position to the new noWait
-        newArray[noWaits.length] = noWait;
-
-        noWaits = newArray;
-
-        return true;
-    }
-
-    /**
-     * Removes a no wait from the list if it is already there.
-     * <p>
-     * @param noWait
-     * @return true if it was already in the array
-     */
-    public synchronized boolean removeNoWait( LateralCacheNoWait<K, V> noWait )
-    {
-        if ( noWait == null )
-        {
-            return false;
-        }
-
-        int position = -1;
-        for ( int i = 0; i < noWaits.length; i++ )
-        {
-            // we know noWait isn't null
-            if ( noWait.equals( noWaits[i] ) )
-            {
-                position = i;
-                break;
-            }
-        }
-
-        if ( position == -1 )
-        {
-            return false;
-        }
-
-        @SuppressWarnings("unchecked") // No generic arrays in java
-        LateralCacheNoWait<K, V>[] newArray = new LateralCacheNoWait[noWaits.length - 1];
-
-        System.arraycopy( noWaits, 0, newArray, 0, position );
-        if ( noWaits.length != position )
-        {
-            System.arraycopy( noWaits, position + 1, newArray, position, noWaits.length - position - 1 );
-        }
-        noWaits = newArray;
-
-        return true;
-    }
-
-    /**
-     * @param ce
-     * @throws IOException
-     */
-    @Override
-    public void update( ICacheElement<K, V> ce )
-        throws IOException
-    {
-        log.debug( "updating through lateral cache facade, noWaits.length = {0}",
-                noWaits.length );
-
-        for (LateralCacheNoWait<K, V> nw : noWaits)
-        {
-            nw.update( ce );
-        }
-    }
-
-    /**
-     * Synchronously reads from the lateral cache.
-     * <p>
-     * @param key
-     * @return ICacheElement
-     */
-    @Override
-    public ICacheElement<K, V> get( K key )
-    {
-        Optional<ICacheElement<K, V>> optional = Arrays.stream(noWaits)
-            .map(nw -> nw.get( key ))
-            .filter(obj -> obj != null)
-            .findFirst();
-
-        if (optional.isPresent())
-        {
-            return optional.get();
-        }
-
-        return null;
-    }
-
-    /**
-     * Gets multiple items from the cache based on the given set of keys.
-     * <p>
-     * @param keys
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache for any of these keys
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMultiple(Set<K> keys)
-    {
-        if ( keys != null && !keys.isEmpty() )
-        {
-            Map<K, ICacheElement<K, V>> elements = keys.stream()
-                .collect(Collectors.toMap(
-                        key -> key,
-                        key -> get(key))).entrySet().stream()
-                    .filter(entry -> entry.getValue() != null)
-                    .collect(Collectors.toMap(
-                            entry -> entry.getKey(),
-                            entry -> entry.getValue()));
-
-            return elements;
-        }
-
-        return new HashMap<>();
-    }
-
-    /**
-     * Synchronously reads from the lateral cache. Get a response from each! This will be slow.
-     * Merge them.
-     * <p>
-     * @param pattern
-     * @return ICacheElement
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMatching(String pattern)
-    {
-        Map<K, ICacheElement<K, V>> elements = new HashMap<>();
-        for (LateralCacheNoWait<K, V> nw : noWaits)
-        {
-            elements.putAll( nw.getMatching( pattern ) );
-        }
-        return elements;
-    }
-
-    /**
-     * Return the keys in this cache.
-     * <p>
-     * @see org.apache.commons.jcs.auxiliary.AuxiliaryCache#getKeySet()
-     */
-    @Override
-    public Set<K> getKeySet() throws IOException
-    {
-        HashSet<K> allKeys = new HashSet<>();
-        for (LateralCacheNoWait<K, V> nw : noWaits)
-        {
-            if ( nw != null )
-            {
-                Set<K> keys = nw.getKeySet();
-                if (keys != null)
-                {
-                    allKeys.addAll( keys );
-                }
-            }
-        }
-        return allKeys;
-    }
-
-    /**
-     * Adds a remove request to the lateral cache.
-     * <p>
-     * @param key
-     * @return always false.
-     */
-    @Override
-    public boolean remove( K key )
-    {
-        Arrays.stream(noWaits).forEach(nw -> nw.remove( key ));
-        return false;
-    }
-
-    /**
-     * Adds a removeAll request to the lateral cache.
-     */
-    @Override
-    public void removeAll()
-    {
-        Arrays.stream(noWaits).forEach(nw -> nw.removeAll());
-    }
-
-    /** Adds a dispose request to the lateral cache. */
-    @Override
-    public void dispose()
-    {
-        try
-        {
-            if ( listener != null )
-            {
-                listener.dispose();
-                listener = null;
-            }
-
-            Arrays.stream(noWaits).forEach(nw -> nw.dispose());
-        }
-        finally
-        {
-            disposed = true;
-        }
-    }
-
-    /**
-     * No lateral invocation.
-     * @return The size value
-     */
-    @Override
-    public int getSize()
-    {
-        return 0;
-        //cache.getSize();
-    }
-
-    /**
-     * Gets the cacheType attribute of the LateralCacheNoWaitFacade object.
-     * <p>
-     * @return The cacheType value
-     */
-    @Override
-    public CacheType getCacheType()
-    {
-        return CacheType.LATERAL_CACHE;
-    }
-
-    /**
-     * Gets the cacheName attribute of the LateralCacheNoWaitFacade object.
-     * <p>
-     * @return The cacheName value
-     */
-    @Override
-    public String getCacheName()
-    {
-        return "";
-        //cache.getCacheName();
-    }
-
-    /**
-     * Gets the status attribute of the LateralCacheNoWaitFacade object
-     * @return The status value
-     */
-    @Override
-    public CacheStatus getStatus()
-    {
-        if (disposed)
-        {
-            return CacheStatus.DISPOSED;
-        }
-
-        if (noWaits.length == 0 || listener != null)
-        {
-            return CacheStatus.ALIVE;
-        }
-
-        List<CacheStatus> statii = Arrays.stream(noWaits)
-                .map(nw -> nw.getStatus())
-                .collect(Collectors.toList());
-
-        // It's alive if ANY of its nowaits is alive
-        if (statii.contains(CacheStatus.ALIVE))
-        {
-            return CacheStatus.ALIVE;
-        }
-        // It's alive if ANY of its nowaits is in error, but
-        // none are alive, then it's in error
-        if (statii.contains(CacheStatus.ERROR))
-        {
-            return CacheStatus.ERROR;
-        }
-
-        // Otherwise, it's been disposed, since it's the only status left
-        return CacheStatus.DISPOSED;
-    }
-
-    /**
-     * @return Returns the AuxiliaryCacheAttributes.
-     */
-    @Override
-    public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
-    {
-        return this.lateralCacheAttributes;
-    }
-
-    /**
-     * @return "LateralCacheNoWaitFacade: " + cacheName;
-     */
-    @Override
-    public String toString()
-    {
-        return "LateralCacheNoWaitFacade: " + cacheName;
-    }
-
-    /**
-     * this won't be called since we don't do ICache logging here.
-     * <p>
-     * @return String
-     */
-    @Override
-    public String getEventLoggingExtraInfo()
-    {
-        return "Lateral Cache No Wait";
-    }
-
-    /**
-     * getStats
-     * @return String
-     */
-    @Override
-    public String getStats()
-    {
-        return getStatistics().toString();
-    }
-
-    /**
-     * @return IStats
-     */
-    @Override
-    public IStats getStatistics()
-    {
-        IStats stats = new Stats();
-        stats.setTypeName( "Lateral Cache No Wait Facade" );
-
-        ArrayList<IStatElement<?>> elems = new ArrayList<>();
-
-        if ( noWaits != null )
-        {
-            elems.add(new StatElement<>( "Number of No Waits", Integer.valueOf(noWaits.length) ) );
-
-            for ( LateralCacheNoWait<K, V> lcnw : noWaits )
-            {
-                if ( lcnw != null )
-                {
-                    // get the stats from the super too
-                    IStats sStats = lcnw.getStatistics();
-                    elems.addAll(sStats.getStatElements());
-                }
-            }
-        }
-
-        stats.setStatElements( elems );
-
-        return stats;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/LateralCommand.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/LateralCommand.java
deleted file mode 100644
index 5c4ff57..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/LateralCommand.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package org.apache.commons.jcs.auxiliary.lateral;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * Enumeration of the available lateral commands
- */
-public enum LateralCommand
-{
-    /** The command for updates */
-    UPDATE,
-
-    /** The command for removes */
-    REMOVE,
-
-    /** The command instructing us to remove all */
-    REMOVEALL,
-
-    /** The command for disposing the cache. */
-    DISPOSE,
-
-    /** Command to return an object. */
-    GET,
-
-    /** Command to return an object. */
-    GET_MATCHING,
-
-    /** Command to get all keys */
-    GET_KEYSET
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/LateralElementDescriptor.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/LateralElementDescriptor.java
deleted file mode 100644
index 55ef04b..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/LateralElementDescriptor.java
+++ /dev/null
@@ -1,83 +0,0 @@
-package org.apache.commons.jcs.auxiliary.lateral;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-
-import java.io.Serializable;
-
-/**
- * This class wraps command to other laterals. It is essentially a
- * JCS-TCP-Lateral packet. The headers specify the action the receiver should
- * take.
- */
-public class LateralElementDescriptor<K, V>
-    implements Serializable
-{
-    /** Don't change */
-    private static final long serialVersionUID = 5268222498076063575L;
-
-    /** The Cache Element that we are distributing. */
-    public ICacheElement<K, V> ce;
-
-    /**
-     * The id of the the source of the request. This is used to prevent infinite
-     * loops.
-     */
-    public long requesterId;
-
-    /** The operation has been requested by the client. */
-    public LateralCommand command = LateralCommand.UPDATE;
-
-    /**
-     * The hashcode value for this element.
-     */
-    public int valHashCode = -1;
-
-    /** Constructor for the LateralElementDescriptor object */
-    public LateralElementDescriptor()
-    {
-        super();
-    }
-
-    /**
-     * Constructor for the LateralElementDescriptor object
-     * <p>
-     * @param ce ICacheElement&lt;K, V&gt; payload
-     */
-    public LateralElementDescriptor( ICacheElement<K, V> ce )
-    {
-        this.ce = ce;
-    }
-
-    /**
-     * @return String, all the important values that can be configured
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder buf = new StringBuilder();
-        buf.append( "\n LateralElementDescriptor " );
-        buf.append( "\n command = [" + this.command + "]" );
-        buf.append( "\n valHashCode = [" + this.valHashCode + "]" );
-        buf.append( "\n ICacheElement = [" + this.ce + "]" );
-        return buf.toString();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/behavior/ILateralCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/behavior/ILateralCacheAttributes.java
deleted file mode 100644
index 07b0183..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/behavior/ILateralCacheAttributes.java
+++ /dev/null
@@ -1,200 +0,0 @@
-package org.apache.commons.jcs.auxiliary.lateral.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes;
-
-/**
- * This interface defines configuration options common to lateral cache plugins.
- * <p>
- * TODO it needs to be trimmed down. The old version had features for every lateral. Now, the
- * individual laterals have their own specific attributes interfaces.
- */
-public interface ILateralCacheAttributes
-    extends AuxiliaryCacheAttributes
-{
-    enum Type
-    {
-        /** HTTP type */
-        HTTP, // 1
-
-        /** UDP type */
-        UDP, // 2
-
-        /** TCP type */
-        TCP, // 3
-
-        /** XMLRPC type */
-        XMLRPC // 4
-    }
-
-    /**
-     * The number of elements the zombie queue will hold. This queue is used to store events if we
-     * loose our connection with the server.
-     */
-    int DEFAULT_ZOMBIE_QUEUE_MAX_SIZE = 1000;
-
-    /**
-     * Sets the httpServer attribute of the ILateralCacheAttributes object
-     * <p>
-     * @param val The new httpServer value
-     */
-    void setHttpServer( String val );
-
-    /**
-     * Gets the httpServer attribute of the ILateralCacheAttributes object
-     * <p>
-     * @return The httpServer value
-     */
-    String getHttpServer();
-
-    /**
-     * Sets the httpListenerPort attribute of the ILateralCacheAttributes object
-     * <p>
-     * @param val The new tcpListenerPort value
-     */
-    void setHttpListenerPort( int val );
-
-    /**
-     * Gets the httpListenerPort attribute of the ILateralCacheAttributes object
-     * <p>
-     * @return The httpListenerPort value
-     */
-    int getHttpListenerPort();
-
-    /**
-     * Sets the httpServers attribute of the LateralCacheAttributes object
-     * <p>
-     * @param val The new httpServers value
-     */
-    void setHttpServers( String val );
-
-    /**
-     * Gets the httpSrvers attribute of the LateralCacheAttributes object
-     * <p>
-     * @return The httpServers value
-     */
-    String getHttpServers();
-
-    /**
-     * Sets the udpMulticastAddr attribute of the ILateralCacheAttributes object
-     * <p>
-     * @param val The new udpMulticastAddr value
-     */
-    void setUdpMulticastAddr( String val );
-
-    /**
-     * Gets the udpMulticastAddr attribute of the ILateralCacheAttributes object
-     * <p>
-     * @return The udpMulticastAddr value
-     */
-    String getUdpMulticastAddr();
-
-    /**
-     * Sets the udpMulticastPort attribute of the ILateralCacheAttributes object
-     * <p>
-     * @param val The new udpMulticastPort value
-     */
-    void setUdpMulticastPort( int val );
-
-    /**
-     * Gets the udpMulticastPort attribute of the ILateralCacheAttributes object
-     * <p>
-     * @return The udpMulticastPort value
-     */
-    int getUdpMulticastPort();
-
-    /**
-     * Sets the transmissionType attribute of the ILateralCacheAttributes object
-     * <p>
-     * @param val The new transmissionType value
-     */
-    void setTransmissionType( Type val );
-
-    /**
-     * Gets the transmissionType attribute of the ILateralCacheAttributes object
-     * <p>
-     * @return The transmissionType value
-     */
-    Type getTransmissionType();
-
-    /**
-     * Sets the transmissionTypeName attribute of the ILateralCacheAttributes object
-     * <p>
-     * @param val The new transmissionTypeName value
-     */
-    void setTransmissionTypeName( String val );
-
-    /**
-     * Gets the transmissionTypeName attribute of the ILateralCacheAttributes object
-     * <p>
-     * @return The transmissionTypeName value
-     */
-    String getTransmissionTypeName();
-
-    /**
-     * Sets the putOnlyMode attribute of the ILateralCacheAttributes. When this is true the lateral
-     * cache will only issue put and remove order and will not try to retrieve elements from other
-     * lateral caches.
-     * <p>
-     * @param val The new transmissionTypeName value
-     */
-    void setPutOnlyMode( boolean val );
-
-    /**
-     * @return The outgoingOnlyMode value. Stops gets from going remote.
-     */
-    boolean getPutOnlyMode();
-
-    /**
-     * @param receive The receive to set.
-     */
-    void setReceive( boolean receive );
-
-    /**
-     * Should a listener be created. By default this is true.
-     * <p>
-     * If this is false the lateral will connect to others but it will not create a listener to
-     * receive.
-     * <p>
-     * It is possible if two laterals are misconfigured that lateral A may have a region R1 that is
-     * not configured for the lateral but another is. And if cache B has region R1 configured for
-     * lateral distribution, A will get messages for R1 but not send them.
-     * <p>
-     * @return true if we should have a listener connection
-     */
-    boolean isReceive();
-
-    /**
-     * The number of elements the zombie queue will hold. This queue is used to store events if we
-     * loose our connection with the server.
-     * <p>
-     * @param zombieQueueMaxSize The zombieQueueMaxSize to set.
-     */
-    void setZombieQueueMaxSize( int zombieQueueMaxSize );
-
-    /**
-     * The number of elements the zombie queue will hold. This queue is used to store events if we
-     * loose our connection with the server.
-     * <p>
-     * @return Returns the zombieQueueMaxSize.
-     */
-    int getZombieQueueMaxSize();
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/behavior/ILateralCacheListener.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/behavior/ILateralCacheListener.java
deleted file mode 100644
index 581f593..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/behavior/ILateralCacheListener.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package org.apache.commons.jcs.auxiliary.lateral.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.behavior.ICacheListener;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
-
-/**
- * Listens for lateral cache event notification.
- */
-public interface ILateralCacheListener<K, V>
-    extends ICacheListener<K, V>
-{
-    /**
-     * Initialize this listener
-     */
-    void init();
-
-    /**
-     * @param cacheMgr
-     *            The cacheMgr to set.
-     */
-    void setCacheManager( ICompositeCacheManager cacheMgr );
-
-    /**
-     * @return Returns the cacheMgr.
-     */
-    ICompositeCacheManager getCacheManager();
-
-    /**
-     * Dispose this listener
-     */
-    void dispose();
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/LateralTCPCacheFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/LateralTCPCacheFactory.java
deleted file mode 100644
index 5374043..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/LateralTCPCacheFactory.java
+++ /dev/null
@@ -1,404 +0,0 @@
-package org.apache.commons.jcs.auxiliary.lateral.socket.tcp;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.StringTokenizer;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.apache.commons.jcs.auxiliary.AbstractAuxiliaryCacheFactory;
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes;
-import org.apache.commons.jcs.auxiliary.lateral.LateralCache;
-import org.apache.commons.jcs.auxiliary.lateral.LateralCacheMonitor;
-import org.apache.commons.jcs.auxiliary.lateral.LateralCacheNoWait;
-import org.apache.commons.jcs.auxiliary.lateral.LateralCacheNoWaitFacade;
-import org.apache.commons.jcs.auxiliary.lateral.behavior.ILateralCacheListener;
-import org.apache.commons.jcs.auxiliary.lateral.socket.tcp.behavior.ITCPLateralCacheAttributes;
-import org.apache.commons.jcs.engine.CacheWatchRepairable;
-import org.apache.commons.jcs.engine.ZombieCacheServiceNonLocal;
-import org.apache.commons.jcs.engine.ZombieCacheWatch;
-import org.apache.commons.jcs.engine.behavior.ICache;
-import org.apache.commons.jcs.engine.behavior.ICacheServiceNonLocal;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.engine.behavior.IShutdownObserver;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-import org.apache.commons.jcs.utils.discovery.UDPDiscoveryManager;
-import org.apache.commons.jcs.utils.discovery.UDPDiscoveryService;
-
-/**
- * Constructs a LateralCacheNoWaitFacade for the given configuration. Each lateral service / local
- * relationship is managed by one manager. This manager can have multiple caches. The remote
- * relationships are consolidated and restored via these managers.
- * <p>
- * The facade provides a front to the composite cache so the implementation is transparent.
- */
-public class LateralTCPCacheFactory
-    extends AbstractAuxiliaryCacheFactory
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog( LateralTCPCacheFactory.class );
-
-    /** Address to service map. */
-    private ConcurrentHashMap<String, ICacheServiceNonLocal<?, ?>> csnlInstances;
-
-    /** Map of available discovery listener instances, keyed by port. */
-    private ConcurrentHashMap<String, LateralTCPDiscoveryListener> lTCPDLInstances;
-
-    /** Monitor thread */
-    private LateralCacheMonitor monitor;
-
-    /**
-     * Wrapper of the lateral cache watch service; or wrapper of a zombie
-     * service if failed to connect.
-     */
-    private CacheWatchRepairable lateralWatch;
-
-    /**
-     * Creates a TCP lateral.
-     * <p>
-     * @param iaca
-     * @param cacheMgr
-     * @param cacheEventLogger
-     * @param elementSerializer
-     * @return LateralCacheNoWaitFacade
-     */
-    @Override
-    public <K, V> LateralCacheNoWaitFacade<K, V> createCache(
-            AuxiliaryCacheAttributes iaca, ICompositeCacheManager cacheMgr,
-           ICacheEventLogger cacheEventLogger, IElementSerializer elementSerializer )
-    {
-        ITCPLateralCacheAttributes lac = (ITCPLateralCacheAttributes) iaca;
-        ArrayList<ICache<K, V>> noWaits = new ArrayList<>();
-
-        // pairs up the tcp servers and set the tcpServer value and
-        // get the manager and then get the cache
-        // no servers are required.
-        if ( lac.getTcpServers() != null )
-        {
-            StringTokenizer it = new StringTokenizer( lac.getTcpServers(), "," );
-            log.debug( "Configured for [{0}] servers.", () -> it.countTokens() );
-
-            while ( it.hasMoreElements() )
-            {
-                String server = (String) it.nextElement();
-                log.debug( "tcp server = {0}", server );
-                ITCPLateralCacheAttributes lacC = (ITCPLateralCacheAttributes) lac.clone();
-                lacC.setTcpServer( server );
-
-                LateralCacheNoWait<K, V> lateralNoWait = createCacheNoWait(lacC, cacheEventLogger, elementSerializer);
-
-                addListenerIfNeeded( lacC, cacheMgr );
-                monitor.addCache(lateralNoWait);
-                noWaits.add( lateralNoWait );
-            }
-        }
-
-        ILateralCacheListener<K, V> listener = createListener( lac, cacheMgr );
-
-        // create the no wait facade.
-        @SuppressWarnings("unchecked") // No generic arrays in java
-        LateralCacheNoWait<K, V>[] lcnwArray = noWaits.toArray( new LateralCacheNoWait[0] );
-        LateralCacheNoWaitFacade<K, V> lcnwf =
-            new LateralCacheNoWaitFacade<>(listener, lcnwArray, lac );
-
-        // create udp discovery if available.
-        createDiscoveryService( lac, lcnwf, cacheMgr, cacheEventLogger, elementSerializer );
-
-        return lcnwf;
-    }
-
-    protected <K, V> LateralCacheNoWait<K, V> createCacheNoWait( ITCPLateralCacheAttributes lca,
-            ICacheEventLogger cacheEventLogger, IElementSerializer elementSerializer )
-    {
-        ICacheServiceNonLocal<K, V> lateralService = getCSNLInstance(lca);
-
-        LateralCache<K, V> cache = new LateralCache<>( lca, lateralService, this.monitor );
-        cache.setCacheEventLogger( cacheEventLogger );
-        cache.setElementSerializer( elementSerializer );
-
-        log.debug( "Created cache for noWait, cache [{0}]", cache );
-
-        LateralCacheNoWait<K, V> lateralNoWait = new LateralCacheNoWait<>( cache );
-        lateralNoWait.setCacheEventLogger( cacheEventLogger );
-        lateralNoWait.setElementSerializer( elementSerializer );
-
-        log.info( "Created LateralCacheNoWait for [{0}] LateralCacheNoWait = [{1}]",
-                lca, lateralNoWait );
-
-        return lateralNoWait;
-    }
-
-    /**
-     * Initialize this factory
-     */
-    @Override
-    public void initialize()
-    {
-        this.csnlInstances = new ConcurrentHashMap<>();
-        this.lTCPDLInstances = new ConcurrentHashMap<>();
-
-        // Create the monitoring daemon thread
-        this.monitor = new LateralCacheMonitor(this);
-        this.monitor.setDaemon( true );
-        this.monitor.start();
-
-        this.lateralWatch = new CacheWatchRepairable();
-        this.lateralWatch.setCacheWatch( new ZombieCacheWatch() );
-    }
-
-    /**
-     * Dispose of this factory, clean up shared resources
-     */
-    @Override
-    public void dispose()
-    {
-        for (ICacheServiceNonLocal<?, ?> service : this.csnlInstances.values())
-        {
-            try
-            {
-                service.dispose("");
-            }
-            catch (IOException e)
-            {
-                log.error("Could not dispose service " + service, e);
-            }
-        }
-
-        this.csnlInstances.clear();
-
-        // TODO: shut down discovery listeners
-        this.lTCPDLInstances.clear();
-
-        if (this.monitor != null)
-        {
-            this.monitor.notifyShutdown();
-            try
-            {
-                this.monitor.join(5000);
-            }
-            catch (InterruptedException e)
-            {
-                // swallow
-            }
-            this.monitor = null;
-        }
-    }
-
-    /**
-     * Returns an instance of the cache service.
-     * <p>
-     * @param lca configuration for the creation of a new service instance
-     *
-     * @return ICacheServiceNonLocal&lt;K, V&gt;
-     */
-    // Need to cast because of common map for all cache services
-    @SuppressWarnings("unchecked")
-    public <K, V> ICacheServiceNonLocal<K, V> getCSNLInstance( ITCPLateralCacheAttributes lca )
-    {
-        String key = lca.getTcpServer();
-
-        csnlInstances.computeIfPresent(key, (name, service) -> {
-            // If service creation did not succeed last time, force retry
-            if (service instanceof ZombieCacheServiceNonLocal)
-            {
-                log.info("Disposing of zombie service instance for [{0}]", name);
-                return null;
-            }
-
-            return service;
-        });
-
-        ICacheServiceNonLocal<K, V> service =
-                (ICacheServiceNonLocal<K, V>) csnlInstances.computeIfAbsent(key, name -> {
-
-                    log.info( "Instance for [{0}] is null, creating", name );
-
-                    // Create the service
-                    try
-                    {
-                        log.info( "Creating TCP service, lca = {0}", lca );
-
-                        return new LateralTCPService<>( lca );
-                    }
-                    catch ( IOException ex )
-                    {
-                        // Failed to connect to the lateral server.
-                        // Configure this LateralCacheManager instance to use the
-                        // "zombie" services.
-                        log.error( "Failure, lateral instance will use zombie service", ex );
-
-                        ICacheServiceNonLocal<K, V> zombieService =
-                                new ZombieCacheServiceNonLocal<>( lca.getZombieQueueMaxSize() );
-
-                        // Notify the cache monitor about the error, and kick off
-                        // the recovery process.
-                        monitor.notifyError();
-
-                        return zombieService;
-                    }
-                });
-
-        return service;
-    }
-
-    /**
-     * Gets the instance attribute of the LateralCacheTCPListener class.
-     * <p>
-     * @param ilca ITCPLateralCacheAttributes
-     * @param cacheManager a reference to the global cache manager
-     *
-     * @return The instance value
-     */
-    private LateralTCPDiscoveryListener getDiscoveryListener(ITCPLateralCacheAttributes ilca, ICompositeCacheManager cacheManager)
-    {
-        String key = ilca.getUdpDiscoveryAddr() + ":" + ilca.getUdpDiscoveryPort();
-
-        LateralTCPDiscoveryListener ins = lTCPDLInstances.computeIfAbsent(key, key1 -> {
-            log.info("Created new discovery listener for cacheName {0} for request {1}",
-                    key1, ilca.getCacheName());
-            return new LateralTCPDiscoveryListener( this.getName(),  cacheManager);
-        });
-
-        return ins;
-    }
-
-    /**
-     * Add listener for receivers
-     * <p>
-     * @param iaca cache configuration attributes
-     * @param cacheMgr the composite cache manager
-     */
-    private void addListenerIfNeeded( ITCPLateralCacheAttributes iaca, ICompositeCacheManager cacheMgr )
-    {
-        // don't create a listener if we are not receiving.
-        if ( iaca.isReceive() )
-        {
-            try
-            {
-                addLateralCacheListener( iaca.getCacheName(),
-                        LateralTCPListener.getInstance( iaca, cacheMgr ) );
-            }
-            catch ( IOException ioe )
-            {
-                log.error("Problem creating lateral listener", ioe);
-            }
-        }
-        else
-        {
-            log.debug( "Not creating a listener since we are not receiving." );
-        }
-    }
-
-    /**
-     * Adds the lateral cache listener to the underlying cache-watch service.
-     * <p>
-     * @param cacheName The feature to be added to the LateralCacheListener attribute
-     * @param listener The feature to be added to the LateralCacheListener attribute
-     * @throws IOException
-     */
-    private <K, V> void addLateralCacheListener( String cacheName, ILateralCacheListener<K, V> listener )
-        throws IOException
-    {
-        synchronized ( this.lateralWatch )
-        {
-            lateralWatch.addCacheListener( cacheName, listener );
-        }
-    }
-
-    /**
-     * Makes sure a listener gets created. It will get monitored as soon as it
-     * is used.
-     * <p>
-     * This should be called by create cache.
-     * <p>
-     * @param attr  ITCPLateralCacheAttributes
-     * @param cacheMgr
-     *
-     * @return the listener if created, else null
-     */
-    private <K, V> ILateralCacheListener<K, V> createListener( ITCPLateralCacheAttributes attr,
-            ICompositeCacheManager cacheMgr )
-    {
-        ILateralCacheListener<K, V> listener = null;
-
-        // don't create a listener if we are not receiving.
-        if ( attr.isReceive() )
-        {
-            log.info( "Getting listener for {0}", attr );
-
-            // make a listener. if one doesn't exist
-            listener = LateralTCPListener.getInstance( attr, cacheMgr );
-
-            // register for shutdown notification
-            cacheMgr.registerShutdownObserver( (IShutdownObserver) listener );
-        }
-        else
-        {
-            log.debug( "Not creating a listener since we are not receiving." );
-        }
-
-        return listener;
-    }
-
-    /**
-     * Creates the discovery service. Only creates this for tcp laterals right now.
-     * <p>
-     * @param lac ITCPLateralCacheAttributes
-     * @param lcnwf
-     * @param cacheMgr
-     * @param cacheEventLogger
-     * @param elementSerializer
-     * @return null if none is created.
-     */
-    private synchronized <K, V> UDPDiscoveryService createDiscoveryService(
-            ITCPLateralCacheAttributes lac,
-            LateralCacheNoWaitFacade<K, V> lcnwf,
-            ICompositeCacheManager cacheMgr,
-            ICacheEventLogger cacheEventLogger,
-            IElementSerializer elementSerializer )
-    {
-        UDPDiscoveryService discovery = null;
-
-        // create the UDP discovery for the TCP lateral
-        if ( lac.isUdpDiscoveryEnabled() )
-        {
-            // One can be used for all regions
-            LateralTCPDiscoveryListener discoveryListener = getDiscoveryListener( lac, cacheMgr );
-            discoveryListener.addNoWaitFacade( lac.getCacheName(), lcnwf );
-
-            // need a factory for this so it doesn't
-            // get dereferenced, also we don't want one for every region.
-            discovery = UDPDiscoveryManager.getInstance().getService( lac.getUdpDiscoveryAddr(),
-                                                                      lac.getUdpDiscoveryPort(),
-                                                                      lac.getTcpListenerPort(), cacheMgr);
-
-            discovery.addParticipatingCacheName( lac.getCacheName() );
-            discovery.addDiscoveryListener( discoveryListener );
-
-            log.info( "Registered TCP lateral cache [{0}] with UDPDiscoveryService.",
-                    () -> lac.getCacheName() );
-        }
-        return discovery;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/LateralTCPDiscoveryListener.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/LateralTCPDiscoveryListener.java
deleted file mode 100644
index 6816ab2..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/LateralTCPDiscoveryListener.java
+++ /dev/null
@@ -1,315 +0,0 @@
-package org.apache.commons.jcs.auxiliary.lateral.socket.tcp;
-
-import java.util.ArrayList;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.AuxiliaryCache;
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes;
-import org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes;
-import org.apache.commons.jcs.auxiliary.lateral.LateralCacheNoWait;
-import org.apache.commons.jcs.auxiliary.lateral.LateralCacheNoWaitFacade;
-import org.apache.commons.jcs.auxiliary.lateral.socket.tcp.behavior.ITCPLateralCacheAttributes;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-import org.apache.commons.jcs.utils.discovery.DiscoveredService;
-import org.apache.commons.jcs.utils.discovery.behavior.IDiscoveryListener;
-
-/**
- * This knows how to add and remove discovered services. It observes UDP discovery events.
- * <p>
- * We can have one listener per region, or one shared by all regions.
- */
-public class LateralTCPDiscoveryListener
-    implements IDiscoveryListener
-{
-    /** The log factory */
-    private static final Log log = LogManager.getLog( LateralTCPDiscoveryListener.class );
-
-    /**
-     * Map of no wait facades. these are used to determine which regions are locally configured to
-     * use laterals.
-     */
-    private final ConcurrentMap<String, LateralCacheNoWaitFacade<?, ?>> facades =
-        new ConcurrentHashMap<>();
-
-    /**
-     * List of regions that are configured differently here than on another server. We keep track of
-     * this to limit the amount of info logging.
-     */
-    private final CopyOnWriteArrayList<String> knownDifferentlyConfiguredRegions =
-        new CopyOnWriteArrayList<>();
-
-    /** The name of the cache factory */
-    private String factoryName;
-
-    /** Reference to the cache manager for auxiliary cache access */
-    private ICompositeCacheManager cacheManager;
-
-    /**
-     * This plugs into the udp discovery system. It will receive add and remove events.
-     * <p>
-     * @param factoryName the name of the related cache factory
-     * @param cacheManager the global cache manager
-     */
-    protected LateralTCPDiscoveryListener( String factoryName, ICompositeCacheManager cacheManager )
-    {
-        this.factoryName = factoryName;
-        this.cacheManager = cacheManager;
-    }
-
-    /**
-     * Adds a nowait facade under this cachename. If one already existed, it will be overridden.
-     * <p>
-     * This adds nowaits to a facade for the region name. If the region has no facade, then it is
-     * not configured to use the lateral cache, and no facade will be created.
-     * <p>
-     * @param cacheName - the region name
-     * @param facade - facade (for region) =&gt; multiple lateral clients.
-     * @return true if the facade was not already registered.
-     */
-    public boolean addNoWaitFacade( String cacheName, LateralCacheNoWaitFacade<?, ?> facade )
-    {
-        boolean isNew = !containsNoWaitFacade( cacheName );
-
-        // override or put anew, it doesn't matter
-        facades.put( cacheName, facade );
-        knownDifferentlyConfiguredRegions.remove( cacheName );
-
-        return isNew;
-    }
-
-    /**
-     * Allows us to see if the facade is present.
-     * <p>
-     * @param cacheName - facades are for a region
-     * @return do we contain the no wait. true if so
-     */
-    public boolean containsNoWaitFacade( String cacheName )
-    {
-        return facades.containsKey( cacheName );
-    }
-
-    /**
-     * Allows us to see if the facade is present and if it has the no wait.
-     * <p>
-     * @param cacheName - facades are for a region
-     * @param noWait - is this no wait in the facade
-     * @return do we contain the no wait. true if so
-     */
-    public <K, V> boolean containsNoWait( String cacheName, LateralCacheNoWait<K, V> noWait )
-    {
-        @SuppressWarnings("unchecked") // Need to cast because of common map for all facades
-        LateralCacheNoWaitFacade<K, V> facade =
-            (LateralCacheNoWaitFacade<K, V>)facades.get( noWait.getCacheName() );
-
-        if ( facade == null )
-        {
-            return false;
-        }
-
-        return facade.containsNoWait( noWait );
-    }
-
-    /**
-     * When a broadcast is received from the UDP Discovery receiver, for each cacheName in the
-     * message, the add no wait will be called here. To add a no wait, the facade is looked up for
-     * this cache name.
-     * <p>
-     * Each region has a facade. The facade contains a list of end points--the other tcp lateral
-     * services.
-     * <p>
-     * @param noWait
-     * @return true if we found the no wait and added it. False if the no wait was not present or if
-     *         we already had it.
-     */
-    protected <K, V> boolean addNoWait( LateralCacheNoWait<K, V> noWait )
-    {
-        @SuppressWarnings("unchecked") // Need to cast because of common map for all facades
-        LateralCacheNoWaitFacade<K, V> facade =
-            (LateralCacheNoWaitFacade<K, V>)facades.get( noWait.getCacheName() );
-        log.debug( "addNoWait > Got facade for {0} = {1}", noWait.getCacheName(), facade );
-
-        if ( facade != null )
-        {
-            boolean isNew = facade.addNoWait( noWait );
-            log.debug( "Called addNoWait, isNew = {0}", isNew );
-            return isNew;
-        }
-        else
-        {
-            if ( knownDifferentlyConfiguredRegions.addIfAbsent( noWait.getCacheName() ) )
-            {
-                log.info( "addNoWait > Different nodes are configured differently "
-                        + "or region [{0}] is not yet used on this side.",
-                        () -> noWait.getCacheName() );
-            }
-            return false;
-        }
-    }
-
-    /**
-     * Look up the facade for the name. If it doesn't exist, then the region is not configured for
-     * use with the lateral cache. If it is present, remove the item from the no wait list.
-     * <p>
-     * @param noWait
-     * @return true if we found the no wait and removed it. False if the no wait was not present.
-     */
-    protected <K, V> boolean removeNoWait( LateralCacheNoWait<K, V> noWait )
-    {
-        @SuppressWarnings("unchecked") // Need to cast because of common map for all facades
-        LateralCacheNoWaitFacade<K, V> facade =
-            (LateralCacheNoWaitFacade<K, V>)facades.get( noWait.getCacheName() );
-        log.debug( "removeNoWait > Got facade for {0} = {1}", noWait.getCacheName(), facade);
-
-        if ( facade != null )
-        {
-            boolean removed = facade.removeNoWait( noWait );
-            log.debug( "Called removeNoWait, removed {0}", removed );
-            return removed;
-        }
-        else
-        {
-            if ( knownDifferentlyConfiguredRegions.addIfAbsent( noWait.getCacheName() ) )
-            {
-                log.info( "addNoWait > Different nodes are configured differently "
-                        + "or region [{0}] is not yet used on this side.",
-                        () -> noWait.getCacheName() );
-            }
-            return false;
-        }
-    }
-
-    /**
-     * Creates the lateral cache if needed.
-     * <p>
-     * We could go to the composite cache manager and get the the cache for the region. This would
-     * force a full configuration of the region. One advantage of this would be that the creation of
-     * the later would go through the factory, which would add the item to the no wait list. But we
-     * don't want to do this. This would force this client to have all the regions as the other.
-     * This might not be desired. We don't want to send or receive for a region here that is either
-     * not used or not configured to use the lateral.
-     * <p>
-     * Right now, I'm afraid that the region will get puts if another instance has the region
-     * configured to use the lateral and our address is configured. This might be a bug, but it
-     * shouldn't happen with discovery.
-     * <p>
-     * @param service
-     */
-    @Override
-    public void addDiscoveredService( DiscoveredService service )
-    {
-        // get a cache and add it to the no waits
-        // the add method should not add the same.
-        // we need the listener port from the original config.
-        ArrayList<String> regions = service.getCacheNames();
-        String serverAndPort = service.getServiceAddress() + ":" + service.getServicePort();
-
-        if ( regions != null )
-        {
-            // for each region get the cache
-            for (String cacheName : regions)
-            {
-                AuxiliaryCache<?, ?> ic = cacheManager.getAuxiliaryCache(factoryName, cacheName);
-
-                log.debug( "Got cache, ic = {0}", ic );
-
-                // add this to the nowaits for this cachename
-                if ( ic != null )
-                {
-                    AuxiliaryCacheAttributes aca = ic.getAuxiliaryCacheAttributes();
-                    if (aca instanceof ITCPLateralCacheAttributes)
-                    {
-                        ITCPLateralCacheAttributes lca = (ITCPLateralCacheAttributes)aca;
-                        if (lca.getTransmissionType() != LateralCacheAttributes.Type.TCP
-                            || !serverAndPort.equals(lca.getTcpServer()) )
-                        {
-                            // skip caches not belonging to this service
-                            continue;
-                        }
-                    }
-
-                    addNoWait( (LateralCacheNoWait<?, ?>) ic );
-                    log.debug( "Called addNoWait for cacheName [{0}]", cacheName );
-                }
-            }
-        }
-        else
-        {
-            log.warn( "No cache names found in message {0}", service );
-        }
-    }
-
-    /**
-     * Removes the lateral cache.
-     * <p>
-     * We need to tell the manager that this instance is bad, so it will reconnect the sender if it
-     * comes back.
-     * <p>
-     * @param service
-     */
-    @Override
-    public void removeDiscoveredService( DiscoveredService service )
-    {
-        // get a cache and add it to the no waits
-        // the add method should not add the same.
-        // we need the listener port from the original config.
-        ArrayList<String> regions = service.getCacheNames();
-        String serverAndPort = service.getServiceAddress() + ":" + service.getServicePort();
-
-        if ( regions != null )
-        {
-            // for each region get the cache
-            for (String cacheName : regions)
-            {
-                AuxiliaryCache<?, ?> ic = cacheManager.getAuxiliaryCache(factoryName, cacheName);
-
-                log.debug( "Got cache, ic = {0}", ic );
-
-                // remove this to the nowaits for this cachename
-                if ( ic != null )
-                {
-                    AuxiliaryCacheAttributes aca = ic.getAuxiliaryCacheAttributes();
-                    if (aca instanceof ITCPLateralCacheAttributes)
-                    {
-                        ITCPLateralCacheAttributes lca = (ITCPLateralCacheAttributes)aca;
-                        if (lca.getTransmissionType() != LateralCacheAttributes.Type.TCP
-                            || !serverAndPort.equals(lca.getTcpServer()) )
-                        {
-                            // skip caches not belonging to this service
-                            continue;
-                        }
-                    }
-
-                    removeNoWait( (LateralCacheNoWait<?, ?>) ic );
-                    log.debug( "Called removeNoWait for cacheName [{0}]", cacheName );
-                }
-            }
-        }
-        else
-        {
-            log.warn( "No cache names found in message {0}", service );
-        }
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/LateralTCPListener.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/LateralTCPListener.java
deleted file mode 100644
index e33e82e..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/LateralTCPListener.java
+++ /dev/null
@@ -1,677 +0,0 @@
-package org.apache.commons.jcs.auxiliary.lateral.socket.tcp;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.Serializable;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.net.SocketAddress;
-import java.net.SocketException;
-import java.net.SocketTimeoutException;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.apache.commons.jcs.auxiliary.lateral.LateralElementDescriptor;
-import org.apache.commons.jcs.auxiliary.lateral.behavior.ILateralCacheListener;
-import org.apache.commons.jcs.auxiliary.lateral.socket.tcp.behavior.ITCPLateralCacheAttributes;
-import org.apache.commons.jcs.engine.CacheInfo;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
-import org.apache.commons.jcs.engine.behavior.IShutdownObserver;
-import org.apache.commons.jcs.engine.control.CompositeCache;
-import org.apache.commons.jcs.io.ObjectInputStreamClassLoaderAware;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-import org.apache.commons.jcs.utils.threadpool.DaemonThreadFactory;
-
-/**
- * Listens for connections from other TCP lateral caches and handles them. The initialization method
- * starts a listening thread, which creates a socket server. When messages are received they are
- * passed to a pooled executor which then calls the appropriate handle method.
- */
-public class LateralTCPListener<K, V>
-    implements ILateralCacheListener<K, V>, IShutdownObserver
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog( LateralTCPListener.class );
-
-    /** How long the server will block on an accept(). 0 is infinite. */
-    private static final int acceptTimeOut = 1000;
-
-    /** The CacheHub this listener is associated with */
-    private transient ICompositeCacheManager cacheManager;
-
-    /** Map of available instances, keyed by port */
-    private static final ConcurrentHashMap<String, ILateralCacheListener<?, ?>> instances =
-        new ConcurrentHashMap<>();
-
-    /** The socket listener */
-    private ListenerThread receiver;
-
-    /** Configuration attributes */
-    private ITCPLateralCacheAttributes tcpLateralCacheAttributes;
-
-    /** The processor. We should probably use an event queue here. */
-    private ExecutorService pooledExecutor;
-
-    /** put count */
-    private int putCnt = 0;
-
-    /** remove count */
-    private int removeCnt = 0;
-
-    /** get count */
-    private int getCnt = 0;
-
-    /**
-     * Use the vmid by default. This can be set for testing. If we ever need to run more than one
-     * per vm, then we need a new technique.
-     */
-    private long listenerId = CacheInfo.listenerId;
-
-    /** is this shut down? */
-    private AtomicBoolean shutdown;
-
-    /** is this terminated? */
-    private AtomicBoolean terminated;
-
-    /**
-     * Gets the instance attribute of the LateralCacheTCPListener class.
-     * <p>
-     * @param ilca ITCPLateralCacheAttributes
-     * @param cacheMgr
-     * @return The instance value
-     */
-    public static <K, V> LateralTCPListener<K, V>
-        getInstance( ITCPLateralCacheAttributes ilca, ICompositeCacheManager cacheMgr )
-    {
-        @SuppressWarnings("unchecked") // Need to cast because of common map for all instances
-        LateralTCPListener<K, V> ins = (LateralTCPListener<K, V>) instances.computeIfAbsent(
-                String.valueOf( ilca.getTcpListenerPort() ),
-                k -> {
-                    LateralTCPListener<K, V> newIns = new LateralTCPListener<>( ilca );
-
-                    newIns.init();
-                    newIns.setCacheManager( cacheMgr );
-
-                    log.info( "Created new listener {0}",
-                            () -> ilca.getTcpListenerPort() );
-
-                    return newIns;
-                });
-
-        return ins;
-    }
-
-    /**
-     * Only need one since it does work for all regions, just reference by multiple region names.
-     * <p>
-     * @param ilca
-     */
-    protected LateralTCPListener( ITCPLateralCacheAttributes ilca )
-    {
-        this.setTcpLateralCacheAttributes( ilca );
-    }
-
-    /**
-     * This starts the ListenerThread on the specified port.
-     */
-    @Override
-    public synchronized void init()
-    {
-        try
-        {
-            int port = getTcpLateralCacheAttributes().getTcpListenerPort();
-            String host = getTcpLateralCacheAttributes().getTcpListenerHost();
-
-            pooledExecutor = Executors.newCachedThreadPool(
-                    new DaemonThreadFactory("JCS-LateralTCPListener-"));
-            terminated = new AtomicBoolean(false);
-            shutdown = new AtomicBoolean(false);
-
-            ServerSocket serverSocket;
-            if (host != null && host.length() > 0)
-            {
-                log.info( "Listening on {0}:{1}", host, port );
-                // Resolve host name
-                InetAddress inetAddress = InetAddress.getByName(host);
-                //Bind the SocketAddress with inetAddress and port
-                SocketAddress endPoint = new InetSocketAddress(inetAddress, port);
-
-                serverSocket = new ServerSocket();
-                serverSocket.bind(endPoint);
-            }
-            else
-            {
-                log.info( "Listening on port {0}", port );
-                serverSocket = new ServerSocket( port );
-            }
-            serverSocket.setSoTimeout( acceptTimeOut );
-
-            receiver = new ListenerThread(serverSocket);
-            receiver.setDaemon( true );
-            receiver.start();
-        }
-        catch ( IOException ex )
-        {
-            throw new IllegalStateException( ex );
-        }
-    }
-
-    /**
-     * Let the lateral cache set a listener_id. Since there is only one listener for all the
-     * regions and every region gets registered? the id shouldn't be set if it isn't zero. If it is
-     * we assume that it is a reconnect.
-     * <p>
-     * By default, the listener id is the vmid.
-     * <p>
-     * The service should set this value. This value will never be changed by a server we connect
-     * to. It needs to be non static, for unit tests.
-     * <p>
-     * The service will use the value it sets in all send requests to the sender.
-     * <p>
-     * @param id The new listenerId value
-     * @throws IOException
-     */
-    @Override
-    public void setListenerId( long id )
-        throws IOException
-    {
-        this.listenerId = id;
-        log.debug( "set listenerId = {0}", id );
-    }
-
-    /**
-     * Gets the listenerId attribute of the LateralCacheTCPListener object
-     * <p>
-     * @return The listenerId value
-     * @throws IOException
-     */
-    @Override
-    public long getListenerId()
-        throws IOException
-    {
-        return this.listenerId;
-    }
-
-    /**
-     * Increments the put count. Gets the cache that was injected by the lateral factory. Calls put
-     * on the cache.
-     * <p>
-     * @see org.apache.commons.jcs.engine.behavior.ICacheListener#handlePut(org.apache.commons.jcs.engine.behavior.ICacheElement)
-     */
-    @Override
-    public void handlePut( ICacheElement<K, V> element )
-        throws IOException
-    {
-        putCnt++;
-        if ( log.isInfoEnabled() && getPutCnt() % 100 == 0 )
-        {
-            log.info( "Put Count (port {0}) = {1}",
-                    () -> getTcpLateralCacheAttributes().getTcpListenerPort(),
-                    () -> getPutCnt() );
-        }
-
-        log.debug( "handlePut> cacheName={0}, key={1}",
-                () -> element.getCacheName(), () -> element.getKey() );
-
-        getCache( element.getCacheName() ).localUpdate( element );
-    }
-
-    /**
-     * Increments the remove count. Gets the cache that was injected by the lateral factory. Calls
-     * remove on the cache.
-     * <p>
-     * @see org.apache.commons.jcs.engine.behavior.ICacheListener#handleRemove(java.lang.String,
-     *      Object)
-     */
-    @Override
-    public void handleRemove( String cacheName, K key )
-        throws IOException
-    {
-        removeCnt++;
-        if ( log.isInfoEnabled() && getRemoveCnt() % 100 == 0 )
-        {
-            log.info( "Remove Count = {0}", () -> getRemoveCnt() );
-        }
-
-        log.debug( "handleRemove> cacheName={0}, key={1}", cacheName, key );
-
-        getCache( cacheName ).localRemove( key );
-    }
-
-    /**
-     * Gets the cache that was injected by the lateral factory. Calls removeAll on the cache.
-     * <p>
-     * @see org.apache.commons.jcs.engine.behavior.ICacheListener#handleRemoveAll(java.lang.String)
-     */
-    @Override
-    public void handleRemoveAll( String cacheName )
-        throws IOException
-    {
-        log.debug( "handleRemoveAll> cacheName={0}", cacheName );
-
-        getCache( cacheName ).localRemoveAll();
-    }
-
-    /**
-     * Gets the cache that was injected by the lateral factory. Calls get on the cache.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @return a ICacheElement
-     * @throws IOException
-     */
-    public ICacheElement<K, V> handleGet( String cacheName, K key )
-        throws IOException
-    {
-        getCnt++;
-        if ( log.isInfoEnabled() && getGetCnt() % 100 == 0 )
-        {
-            log.info( "Get Count (port {0}) = {1}",
-                    () -> getTcpLateralCacheAttributes().getTcpListenerPort(),
-                    () -> getGetCnt() );
-        }
-
-        log.debug( "handleGet> cacheName={0}, key={1}", cacheName, key );
-
-        return getCache( cacheName ).localGet( key );
-    }
-
-    /**
-     * Gets the cache that was injected by the lateral factory. Calls get on the cache.
-     * <p>
-     * @param cacheName the name of the cache
-     * @param pattern the matching pattern
-     * @return Map
-     * @throws IOException
-     */
-    public Map<K, ICacheElement<K, V>> handleGetMatching( String cacheName, String pattern )
-        throws IOException
-    {
-        getCnt++;
-        if ( log.isInfoEnabled() && getGetCnt() % 100 == 0 )
-        {
-            log.info( "GetMatching Count (port {0}) = {1}",
-                    () -> getTcpLateralCacheAttributes().getTcpListenerPort(),
-                    () -> getGetCnt() );
-        }
-
-        log.debug( "handleGetMatching> cacheName={0}, pattern={1}", cacheName, pattern );
-
-        return getCache( cacheName ).localGetMatching( pattern );
-    }
-
-    /**
-     * Gets the cache that was injected by the lateral factory. Calls getKeySet on the cache.
-     * <p>
-     * @param cacheName the name of the cache
-     * @return a set of keys
-     * @throws IOException
-     */
-    public Set<K> handleGetKeySet( String cacheName ) throws IOException
-    {
-    	return getCache( cacheName ).getKeySet(true);
-    }
-
-    /**
-     * This marks this instance as terminated.
-     * <p>
-     * @see org.apache.commons.jcs.engine.behavior.ICacheListener#handleDispose(java.lang.String)
-     */
-    @Override
-    public void handleDispose( String cacheName )
-        throws IOException
-    {
-        log.info( "handleDispose > cacheName={0} | Ignoring message. "
-                + "Do not dispose from remote.", cacheName );
-
-        // TODO handle active deregistration, rather than passive detection
-        terminated.set(true);
-    }
-
-    @Override
-    public synchronized void dispose()
-    {
-        terminated.set(true);
-        notify();
-
-        pooledExecutor.shutdownNow();
-    }
-
-    /**
-     * Gets the cacheManager attribute of the LateralCacheTCPListener object.
-     * <p>
-     * Normally this is set by the factory. If it wasn't set the listener defaults to the expected
-     * singleton behavior of the cache manager.
-     * <p>
-     * @param name
-     * @return CompositeCache
-     */
-    protected CompositeCache<K, V> getCache( String name )
-    {
-        return getCacheManager().getCache( name );
-    }
-
-    /**
-     * This is roughly the number of updates the lateral has received.
-     * <p>
-     * @return Returns the putCnt.
-     */
-    public int getPutCnt()
-    {
-        return putCnt;
-    }
-
-    /**
-     * @return Returns the getCnt.
-     */
-    public int getGetCnt()
-    {
-        return getCnt;
-    }
-
-    /**
-     * @return Returns the removeCnt.
-     */
-    public int getRemoveCnt()
-    {
-        return removeCnt;
-    }
-
-    /**
-     * @param cacheMgr The cacheMgr to set.
-     */
-    @Override
-    public void setCacheManager( ICompositeCacheManager cacheMgr )
-    {
-        this.cacheManager = cacheMgr;
-    }
-
-    /**
-     * @return Returns the cacheMgr.
-     */
-    @Override
-    public ICompositeCacheManager getCacheManager()
-    {
-        return cacheManager;
-    }
-
-    /**
-     * @param tcpLateralCacheAttributes The tcpLateralCacheAttributes to set.
-     */
-    public void setTcpLateralCacheAttributes( ITCPLateralCacheAttributes tcpLateralCacheAttributes )
-    {
-        this.tcpLateralCacheAttributes = tcpLateralCacheAttributes;
-    }
-
-    /**
-     * @return Returns the tcpLateralCacheAttributes.
-     */
-    public ITCPLateralCacheAttributes getTcpLateralCacheAttributes()
-    {
-        return tcpLateralCacheAttributes;
-    }
-
-    /**
-     * Processes commands from the server socket. There should be one listener for each configured
-     * TCP lateral.
-     */
-    public class ListenerThread
-        extends Thread
-    {
-        /** The socket listener */
-        private final ServerSocket serverSocket;
-
-        /**
-         * Constructor
-         *
-         * @param serverSocket
-         */
-        public ListenerThread(ServerSocket serverSocket)
-        {
-            super();
-            this.serverSocket = serverSocket;
-        }
-
-        /** Main processing method for the ListenerThread object */
-        @SuppressWarnings("synthetic-access")
-        @Override
-        public void run()
-        {
-            try (ServerSocket ssck = serverSocket)
-            {
-                ConnectionHandler handler;
-
-                outer: while ( true )
-                {
-                    log.debug( "Waiting for clients to connect " );
-
-                    Socket socket = null;
-                    inner: while (true)
-                    {
-                        // Check to see if we've been asked to exit, and exit
-                        if (terminated.get())
-                        {
-                            log.debug("Thread terminated, exiting gracefully");
-                            break outer;
-                        }
-
-                        try
-                        {
-                            socket = ssck.accept();
-                            break inner;
-                        }
-                        catch (SocketTimeoutException e)
-                        {
-                            // No problem! We loop back up!
-                            continue inner;
-                        }
-                    }
-
-                    if ( socket != null && log.isDebugEnabled() )
-                    {
-                        InetAddress inetAddress = socket.getInetAddress();
-                        log.debug( "Connected to client at {0}", inetAddress );
-                    }
-
-                    handler = new ConnectionHandler( socket );
-                    pooledExecutor.execute( handler );
-                }
-            }
-            catch ( IOException e )
-            {
-                log.error( "Exception caught in TCP listener", e );
-            }
-        }
-    }
-
-    /**
-     * A Separate thread that runs when a command comes into the LateralTCPReceiver.
-     */
-    public class ConnectionHandler
-        implements Runnable
-    {
-        /** The socket connection, passed in via constructor */
-        private final Socket socket;
-
-        /**
-         * Construct for a given socket
-         * @param socket
-         */
-        public ConnectionHandler( Socket socket )
-        {
-            this.socket = socket;
-        }
-
-        /**
-         * Main processing method for the LateralTCPReceiverConnection object
-         */
-        @Override
-        @SuppressWarnings({"unchecked", // Need to cast from Object
-            "synthetic-access" })
-        public void run()
-        {
-            try (ObjectInputStream ois =
-                    new ObjectInputStreamClassLoaderAware( socket.getInputStream(), null ))
-            {
-                while ( true )
-                {
-                    LateralElementDescriptor<K, V> led =
-                            (LateralElementDescriptor<K, V>) ois.readObject();
-
-                    if ( led == null )
-                    {
-                        log.debug( "LateralElementDescriptor is null" );
-                        continue;
-                    }
-                    if ( led.requesterId == getListenerId() )
-                    {
-                        log.debug( "from self" );
-                    }
-                    else
-                    {
-                        log.debug( "receiving LateralElementDescriptor from another led = {0}",
-                                led );
-
-                        handle( led );
-                    }
-                }
-            }
-            catch ( EOFException e )
-            {
-                log.info( "Caught EOFException, closing connection.", e );
-            }
-            catch ( SocketException e )
-            {
-                log.info( "Caught SocketException, closing connection.", e );
-            }
-            catch ( Exception e )
-            {
-                log.error( "Unexpected exception.", e );
-            }
-        }
-
-        /**
-         * This calls the appropriate method, based on the command sent in the Lateral element
-         * descriptor.
-         * <p>
-         * @param led
-         * @throws IOException
-         */
-        @SuppressWarnings("synthetic-access")
-        private void handle( LateralElementDescriptor<K, V> led )
-            throws IOException
-        {
-            String cacheName = led.ce.getCacheName();
-            K key = led.ce.getKey();
-            Serializable obj = null;
-
-            switch (led.command)
-            {
-                case UPDATE:
-                    handlePut( led.ce );
-                    break;
-
-                case REMOVE:
-                    // if a hashcode was given and filtering is on
-                    // check to see if they are the same
-                    // if so, then don't remove, otherwise issue a remove
-                    if ( led.valHashCode != -1 )
-                    {
-                        if ( getTcpLateralCacheAttributes().isFilterRemoveByHashCode() )
-                        {
-                            ICacheElement<K, V> test = getCache( cacheName ).localGet( key );
-                            if ( test != null )
-                            {
-                                if ( test.getVal().hashCode() == led.valHashCode )
-                                {
-                                    log.debug( "Filtering detected identical hashCode [{0}], "
-                                            + "not issuing a remove for led {1}",
-                                            led.valHashCode, led );
-                                    return;
-                                }
-                                else
-                                {
-                                    log.debug( "Different hashcodes, in cache [{0}] sent [{1}]",
-                                            test.getVal().hashCode(), led.valHashCode );
-                                }
-                            }
-                        }
-                    }
-                    handleRemove( cacheName, key );
-                    break;
-
-                case REMOVEALL:
-                    handleRemoveAll( cacheName );
-                    break;
-
-                case GET:
-                    obj = handleGet( cacheName, key );
-                    break;
-
-                case GET_MATCHING:
-                    obj = (Serializable) handleGetMatching( cacheName, (String) key );
-                    break;
-
-                case GET_KEYSET:
-                	obj = (Serializable) handleGetKeySet(cacheName);
-                    break;
-
-                default: break;
-            }
-
-            if (obj != null)
-            {
-                ObjectOutputStream oos = new ObjectOutputStream( socket.getOutputStream() );
-                oos.writeObject( obj );
-                oos.flush();
-            }
-        }
-    }
-
-    /**
-     * Shuts down the receiver.
-     */
-    @Override
-    public void shutdown()
-    {
-        if ( shutdown.compareAndSet(false, true) )
-        {
-            log.info( "Shutting down TCP Lateral receiver." );
-
-            receiver.interrupt();
-        }
-        else
-        {
-            log.debug( "Shutdown already called." );
-        }
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/LateralTCPSender.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/LateralTCPSender.java
deleted file mode 100644
index 69b23b2..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/LateralTCPSender.java
+++ /dev/null
@@ -1,259 +0,0 @@
-package org.apache.commons.jcs.auxiliary.lateral.socket.tcp;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-
-import org.apache.commons.jcs.auxiliary.lateral.LateralElementDescriptor;
-import org.apache.commons.jcs.auxiliary.lateral.socket.tcp.behavior.ITCPLateralCacheAttributes;
-import org.apache.commons.jcs.io.ObjectInputStreamClassLoaderAware;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * This class is based on the log4j SocketAppender class. I'm using a different repair structure, so
- * it is significantly different.
- */
-public class LateralTCPSender
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog( LateralTCPSender.class );
-
-    /** Config */
-    private int socketOpenTimeOut;
-    private int socketSoTimeOut;
-
-    /** The stream from the server connection. */
-    private ObjectOutputStream oos;
-
-    /** The socket connection with the server. */
-    private Socket socket;
-
-    /** how many messages sent */
-    private int sendCnt = 0;
-
-    /** Use to synchronize multiple threads that may be trying to get. */
-    private final Object getLock = new int[0];
-
-    /**
-     * Constructor for the LateralTCPSender object.
-     * <p>
-     * @param lca
-     * @throws IOException
-     */
-    public LateralTCPSender( ITCPLateralCacheAttributes lca )
-        throws IOException
-    {
-        this.socketOpenTimeOut = lca.getOpenTimeOut();
-        this.socketSoTimeOut = lca.getSocketTimeOut();
-
-        String p1 = lca.getTcpServer();
-        if ( p1 == null )
-        {
-            throw new IOException( "Invalid server (null)" );
-        }
-
-        String h2 = p1.substring( 0, p1.indexOf( ":" ) );
-        int po = Integer.parseInt( p1.substring( p1.indexOf( ":" ) + 1 ) );
-        log.debug( "h2 = {0}, po = {1}", h2, po );
-
-        if ( h2.length() == 0 )
-        {
-            throw new IOException( "Cannot connect to invalid address [" + h2 + ":" + po + "]" );
-        }
-
-        init( h2, po );
-    }
-
-    /**
-     * Creates a connection to a TCP server.
-     * <p>
-     * @param host
-     * @param port
-     * @throws IOException
-     */
-    protected void init( String host, int port )
-        throws IOException
-    {
-        try
-        {
-            log.info( "Attempting connection to [{0}]", host );
-
-            // have time out socket open do this for us
-            try
-            {
-                socket = new Socket();
-                socket.connect( new InetSocketAddress( host, port ), this.socketOpenTimeOut );
-            }
-            catch ( IOException ioe )
-            {
-                if (socket != null)
-                {
-                    socket.close();
-                }
-
-                throw new IOException( "Cannot connect to " + host + ":" + port, ioe );
-            }
-
-            socket.setSoTimeout( socketSoTimeOut );
-            synchronized ( this )
-            {
-                oos = new ObjectOutputStream( socket.getOutputStream() );
-            }
-        }
-        catch ( java.net.ConnectException e )
-        {
-            log.debug( "Remote host [{0}] refused connection.", host );
-            throw e;
-        }
-        catch ( IOException e )
-        {
-            log.debug( "Could not connect to [{0}]", host, e );
-            throw e;
-        }
-    }
-
-    /**
-     * Sends commands to the lateral cache listener.
-     * <p>
-     * @param led
-     * @throws IOException
-     */
-    public <K, V> void send( LateralElementDescriptor<K, V> led )
-        throws IOException
-    {
-        sendCnt++;
-        if ( log.isInfoEnabled() && sendCnt % 100 == 0 )
-        {
-            log.info( "Send Count (port {0}) = {1}", socket.getPort(), sendCnt );
-        }
-
-        log.debug( "sending LateralElementDescriptor" );
-
-        if ( led == null )
-        {
-            return;
-        }
-
-        if ( oos == null )
-        {
-            throw new IOException( "No remote connection is available for LateralTCPSender." );
-        }
-
-        synchronized ( this.getLock )
-        {
-            oos.writeUnshared( led );
-            oos.flush();
-        }
-    }
-
-    /**
-     * Sends commands to the lateral cache listener and gets a response. I'm afraid that we could
-     * get into a pretty bad blocking situation here. This needs work. I just wanted to get some
-     * form of get working. However, get is not recommended for performance reasons. If you have 10
-     * laterals, then you have to make 10 failed gets to find out none of the caches have the item.
-     * <p>
-     * @param led
-     * @return ICacheElement
-     * @throws IOException
-     */
-    public <K, V> Object sendAndReceive( LateralElementDescriptor<K, V> led )
-        throws IOException
-    {
-        if ( led == null )
-        {
-            return null;
-        }
-
-        if ( oos == null )
-        {
-            throw new IOException( "No remote connection is available for LateralTCPSender." );
-        }
-
-        Object response = null;
-
-        // Synchronized to insure that the get requests to server from this
-        // sender and the responses are processed in order, else you could
-        // return the wrong item from the cache.
-        // This is a big block of code. May need to re-think this strategy.
-        // This may not be necessary.
-        // Normal puts, etc to laterals do not have to be synchronized.
-        synchronized ( this.getLock )
-        {
-            try
-            {
-                // clean up input stream, nothing should be there yet.
-                if ( socket.getInputStream().available() > 0 )
-                {
-                    socket.getInputStream().read( new byte[socket.getInputStream().available()] );
-                }
-            }
-            catch ( IOException ioe )
-            {
-                log.error( "Problem cleaning socket before send {0}", socket, ioe );
-                throw ioe;
-            }
-
-            // write object to listener
-            oos.writeUnshared( led );
-            oos.flush();
-
-            try (ObjectInputStream ois = new ObjectInputStreamClassLoaderAware( socket.getInputStream(), null ))
-            {
-                socket.setSoTimeout( socketSoTimeOut );
-                response = ois.readObject();
-            }
-            catch ( IOException ioe )
-            {
-                String message = "Could not open ObjectInputStream to " + socket +
-                    " SoTimeout [" + socket.getSoTimeout() +
-                    "] Connected [" + socket.isConnected() + "]";
-                log.error( message, ioe );
-                throw ioe;
-            }
-            catch ( Exception e )
-            {
-                log.error( e );
-            }
-        }
-
-        return response;
-    }
-
-    /**
-     * Closes connection used by all LateralTCPSenders for this lateral connection. Dispose request
-     * should come into the facade and be sent to all lateral cache services. The lateral cache
-     * service will then call this method.
-     * <p>
-     * @throws IOException
-     */
-    public void dispose()
-        throws IOException
-    {
-        log.info( "Dispose called" );
-        // WILL CLOSE CONNECTION USED BY ALL
-        oos.close();
-        socket.close();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/LateralTCPService.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/LateralTCPService.java
deleted file mode 100644
index 553ad95..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/LateralTCPService.java
+++ /dev/null
@@ -1,451 +0,0 @@
-package org.apache.commons.jcs.auxiliary.lateral.socket.tcp;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.nio.charset.StandardCharsets;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.commons.jcs.auxiliary.lateral.LateralCommand;
-import org.apache.commons.jcs.auxiliary.lateral.LateralElementDescriptor;
-import org.apache.commons.jcs.auxiliary.lateral.socket.tcp.behavior.ITCPLateralCacheAttributes;
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.CacheInfo;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheServiceNonLocal;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * A lateral cache service implementation. Does not implement getGroupKey
- * TODO: Remove generics
- */
-public class LateralTCPService<K, V>
-    implements ICacheServiceNonLocal<K, V>
-{
-    /** The logger. */
-    private static final Log log = LogManager.getLog( LateralTCPService.class );
-
-    /** special configuration */
-    private boolean allowPut;
-    private boolean allowGet;
-    private boolean issueRemoveOnPut;
-
-    /** Sends to another lateral. */
-    private LateralTCPSender sender;
-
-    /** use the vmid by default */
-    private long listenerId = CacheInfo.listenerId;
-
-    /**
-     * Constructor for the LateralTCPService object
-     * <p>
-     * @param lca ITCPLateralCacheAttributes
-     * @throws IOException
-     */
-    public LateralTCPService( ITCPLateralCacheAttributes lca )
-        throws IOException
-    {
-        this.allowGet = lca.isAllowGet();
-        this.allowPut = lca.isAllowPut();
-        this.issueRemoveOnPut = lca.isIssueRemoveOnPut();
-
-        try
-        {
-            sender = new LateralTCPSender( lca );
-
-            log.debug( "Created sender to [{0}]", () -> lca.getTcpServer() );
-        }
-        catch ( IOException e )
-        {
-            // log.error( "Could not create sender", e );
-            // This gets thrown over and over in recovery mode.
-            // The stack trace isn't useful here.
-            log.error( "Could not create sender to [{0}] -- {1}", lca.getTcpServer(), e.getMessage());
-            throw e;
-        }
-    }
-
-    /**
-     * @param item
-     * @throws IOException
-     */
-    @Override
-    public void update( ICacheElement<K, V> item )
-        throws IOException
-    {
-        update( item, getListenerId() );
-    }
-
-    /**
-     * If put is allowed, we will issue a put. If issue put on remove is configured, we will issue a
-     * remove. Either way, we create a lateral element descriptor, which is essentially a JCS TCP
-     * packet. It describes what operation the receiver should take when it gets the packet.
-     * <p>
-     * @see org.apache.commons.jcs.engine.behavior.ICacheServiceNonLocal#update(org.apache.commons.jcs.engine.behavior.ICacheElement,
-     *      long)
-     */
-    @Override
-    public void update( ICacheElement<K, V> item, long requesterId )
-        throws IOException
-    {
-        // if we don't allow put, see if we should remove on put
-        if ( !this.allowPut &&
-            // if we can't remove on put, and we can't put then return
-            !this.issueRemoveOnPut )
-        {
-            return;
-        }
-
-        // if we shouldn't remove on put, then put
-        if ( !this.issueRemoveOnPut )
-        {
-            LateralElementDescriptor<K, V> led = new LateralElementDescriptor<>( item );
-            led.requesterId = requesterId;
-            led.command = LateralCommand.UPDATE;
-            sender.send( led );
-        }
-        // else issue a remove with the hashcode for remove check on
-        // on the other end, this will be a server config option
-        else
-        {
-            log.debug( "Issuing a remove for a put" );
-
-            // set the value to null so we don't send the item
-            CacheElement<K, V> ce = new CacheElement<>( item.getCacheName(), item.getKey(), null );
-            LateralElementDescriptor<K, V> led = new LateralElementDescriptor<>( ce );
-            led.requesterId = requesterId;
-            led.command = LateralCommand.REMOVE;
-            led.valHashCode = item.getVal().hashCode();
-            sender.send( led );
-        }
-    }
-
-    /**
-     * Uses the default listener id and calls the next remove method.
-     * <p>
-     * @see org.apache.commons.jcs.engine.behavior.ICacheService#remove(String, Object)
-     */
-    @Override
-    public void remove( String cacheName, K key )
-        throws IOException
-    {
-        remove( cacheName, key, getListenerId() );
-    }
-
-    /**
-     * Wraps the key in a LateralElementDescriptor.
-     * <p>
-     * @see org.apache.commons.jcs.engine.behavior.ICacheServiceNonLocal#remove(String, Object, long)
-     */
-    @Override
-    public void remove( String cacheName, K key, long requesterId )
-        throws IOException
-    {
-        CacheElement<K, V> ce = new CacheElement<>( cacheName, key, null );
-        LateralElementDescriptor<K, V> led = new LateralElementDescriptor<>( ce );
-        led.requesterId = requesterId;
-        led.command = LateralCommand.REMOVE;
-        sender.send( led );
-    }
-
-    /**
-     * Does nothing.
-     * <p>
-     * @throws IOException
-     */
-    @Override
-    public void release()
-        throws IOException
-    {
-        // nothing needs to be done
-    }
-
-    /**
-     * Will close the connection.
-     * <p>
-     * @param cacheName
-     * @throws IOException
-     */
-    @Override
-    public void dispose( String cacheName )
-        throws IOException
-    {
-        sender.dispose();
-    }
-
-    /**
-     * @param cacheName
-     * @param key
-     * @return ICacheElement&lt;K, V&gt; if found.
-     * @throws IOException
-     */
-    @Override
-    public ICacheElement<K, V> get( String cacheName, K key )
-        throws IOException
-    {
-        return get( cacheName, key, getListenerId() );
-    }
-
-    /**
-     * If get is allowed, we will issues a get request.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @param requesterId
-     * @return ICacheElement&lt;K, V&gt; if found.
-     * @throws IOException
-     */
-    @Override
-    public ICacheElement<K, V> get( String cacheName, K key, long requesterId )
-        throws IOException
-    {
-        // if get is not allowed return
-        if ( this.allowGet )
-        {
-            CacheElement<K, V> ce = new CacheElement<>( cacheName, key, null );
-            LateralElementDescriptor<K, V> led = new LateralElementDescriptor<>( ce );
-            // led.requesterId = requesterId; // later
-            led.command = LateralCommand.GET;
-            @SuppressWarnings("unchecked") // Need to cast from Object
-            ICacheElement<K, V> response = (ICacheElement<K, V>)sender.sendAndReceive( led );
-            if ( response != null )
-            {
-                return response;
-            }
-            return null;
-        }
-        else
-        {
-            // nothing needs to be done
-            return null;
-        }
-    }
-
-    /**
-     * If allow get is true, we will issue a getmatching query.
-     * <p>
-     * @param cacheName
-     * @param pattern
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache matching the pattern.
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern )
-        throws IOException
-    {
-        return getMatching( cacheName, pattern, getListenerId() );
-    }
-
-    /**
-     * If allow get is true, we will issue a getmatching query.
-     * <p>
-     * @param cacheName
-     * @param pattern
-     * @param requesterId - our identity
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache matching the pattern.
-     * @throws IOException
-     */
-    @Override
-    @SuppressWarnings("unchecked") // Need to cast from Object
-    public Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern, long requesterId )
-        throws IOException
-    {
-        // if get is not allowed return
-        if ( this.allowGet )
-        {
-            CacheElement<String, String> ce = new CacheElement<>( cacheName, pattern, null );
-            LateralElementDescriptor<String, String> led = new LateralElementDescriptor<>( ce );
-            // led.requesterId = requesterId; // later
-            led.command = LateralCommand.GET_MATCHING;
-
-            Object response = sender.sendAndReceive( led );
-            if ( response != null )
-            {
-                return (Map<K, ICacheElement<K, V>>) response;
-            }
-            return Collections.emptyMap();
-        }
-        else
-        {
-            // nothing needs to be done
-            return null;
-        }
-    }
-
-    /**
-     * Gets multiple items from the cache based on the given set of keys.
-     * <p>
-     * @param cacheName
-     * @param keys
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache for any of these keys
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys )
-        throws IOException
-    {
-        return getMultiple( cacheName, keys, getListenerId() );
-    }
-
-    /**
-     * This issues a separate get for each item.
-     * <p>
-     * TODO We should change this. It should issue one request.
-     * <p>
-     * @param cacheName
-     * @param keys
-     * @param requesterId
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache for any of these keys
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys, long requesterId )
-        throws IOException
-    {
-        Map<K, ICacheElement<K, V>> elements = new HashMap<>();
-
-        if ( keys != null && !keys.isEmpty() )
-        {
-            for (K key : keys)
-            {
-                ICacheElement<K, V> element = get( cacheName, key );
-
-                if ( element != null )
-                {
-                    elements.put( key, element );
-                }
-            }
-        }
-        return elements;
-    }
-
-    /**
-     * Return the keys in this cache.
-     * <p>
-     * @param cacheName the name of the cache region
-     * @see org.apache.commons.jcs.auxiliary.AuxiliaryCache#getKeySet()
-     */
-    @Override
-    @SuppressWarnings("unchecked") // Need cast from Object
-    public Set<K> getKeySet(String cacheName) throws IOException
-    {
-        CacheElement<String, String> ce = new CacheElement<>(cacheName, null, null);
-        LateralElementDescriptor<String, String> led = new LateralElementDescriptor<>(ce);
-        // led.requesterId = requesterId; // later
-        led.command = LateralCommand.GET_KEYSET;
-        Object response = sender.sendAndReceive(led);
-        if (response != null)
-        {
-            return (Set<K>) response;
-        }
-
-        return null;
-    }
-
-    /**
-     * @param cacheName
-     * @throws IOException
-     */
-    @Override
-    public void removeAll( String cacheName )
-        throws IOException
-    {
-        removeAll( cacheName, getListenerId() );
-    }
-
-    /**
-     * @param cacheName
-     * @param requesterId
-     * @throws IOException
-     */
-    @Override
-    public void removeAll( String cacheName, long requesterId )
-        throws IOException
-    {
-        CacheElement<String, String> ce = new CacheElement<>( cacheName, "ALL", null );
-        LateralElementDescriptor<String, String> led = new LateralElementDescriptor<>( ce );
-        led.requesterId = requesterId;
-        led.command = LateralCommand.REMOVEALL;
-        sender.send( led );
-    }
-
-    /**
-     * @param args
-     */
-    public static void main( String args[] )
-    {
-        try
-        {
-            LateralTCPSender sender = new LateralTCPSender( new TCPLateralCacheAttributes() );
-
-            // process user input till done
-            boolean notDone = true;
-            String message = null;
-            // wait to dispose
-            BufferedReader br = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8));
-
-            while ( notDone )
-            {
-                System.out.println( "enter message:" );
-                message = br.readLine();
-
-                if (message == null)
-                {
-                    notDone = false;
-                    continue;
-                }
-
-                CacheElement<String, String> ce = new CacheElement<>( "test", "test", message );
-                LateralElementDescriptor<String, String> led = new LateralElementDescriptor<>( ce );
-                sender.send( led );
-            }
-        }
-        catch ( IOException e )
-        {
-            System.out.println( e.toString() );
-        }
-    }
-
-    /**
-     * @param listernId The listernId to set.
-     */
-    protected void setListenerId( long listernId )
-    {
-        this.listenerId = listernId;
-    }
-
-    /**
-     * @return Returns the listernId.
-     */
-    protected long getListenerId()
-    {
-        return listenerId;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/TCPLateralCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/TCPLateralCacheAttributes.java
deleted file mode 100644
index 26ed199..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/TCPLateralCacheAttributes.java
+++ /dev/null
@@ -1,407 +0,0 @@
-package org.apache.commons.jcs.auxiliary.lateral.socket.tcp;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes;
-import org.apache.commons.jcs.auxiliary.lateral.socket.tcp.behavior.ITCPLateralCacheAttributes;
-
-/**
- * This interface defines functions that are particular to the TCP Lateral Cache plugin. It extends
- * the generic LateralCacheAttributes interface which in turn extends the AuxiliaryCache interface.
- */
-public class TCPLateralCacheAttributes
-    extends LateralCacheAttributes
-    implements ITCPLateralCacheAttributes
-{
-    /** Don't change. */
-    private static final long serialVersionUID = 1077889204513905220L;
-
-    /** default */
-    private static final String DEFAULT_UDP_DISCOVERY_ADDRESS = "228.5.6.7";
-
-    /** default */
-    private static final int DEFAULT_UDP_DISCOVERY_PORT = 6789;
-
-    /** default */
-    private static final boolean DEFAULT_UDP_DISCOVERY_ENABLED = true;
-
-    /** default */
-    private static final boolean DEFAULT_ALLOW_GET = true;
-
-    /** default */
-    private static final boolean DEFAULT_ALLOW_PUT = true;
-
-    /** default */
-    private static final boolean DEFAULT_ISSUE_REMOVE_FOR_PUT = false;
-
-    /** default */
-    private static final boolean DEFAULT_FILTER_REMOVE_BY_HASH_CODE = true;
-
-    /** default - Only block for 1 second before timing out on a read.*/
-    private static final int DEFAULT_SOCKET_TIME_OUT = 1000;
-
-    /** default - Only block for 2 seconds before timing out on startup.*/
-    private static final int DEFAULT_OPEN_TIMEOUT = 2000;
-
-    /** TCP -------------------------------------------- */
-    private String tcpServers = "";
-
-    /** used to identify the service that this manager will be operating on */
-    private String tcpServer = "";
-
-    /** The port */
-    private int tcpListenerPort = 0;
-
-    /** The host */
-    private String tcpListenerHost = "";
-
-    /** udp discovery for tcp server */
-    private String udpDiscoveryAddr = DEFAULT_UDP_DISCOVERY_ADDRESS;
-
-    /** discovery port */
-    private int udpDiscoveryPort = DEFAULT_UDP_DISCOVERY_PORT;
-
-    /** discovery switch */
-    private boolean udpDiscoveryEnabled = DEFAULT_UDP_DISCOVERY_ENABLED;
-
-    /** can we put */
-    private boolean allowPut = DEFAULT_ALLOW_GET;
-
-    /** can we go laterally for a get */
-    private boolean allowGet = DEFAULT_ALLOW_PUT;
-
-    /** call remove when there is a put */
-    private boolean issueRemoveOnPut = DEFAULT_ISSUE_REMOVE_FOR_PUT;
-
-    /** don't remove it the hashcode is the same */
-    private boolean filterRemoveByHashCode = DEFAULT_FILTER_REMOVE_BY_HASH_CODE;
-
-    /** Only block for socketTimeOut seconds before timing out on a read.  */
-    private int socketTimeOut = DEFAULT_SOCKET_TIME_OUT;
-
-    /** Only block for openTimeOut seconds before timing out on startup. */
-    private int openTimeOut = DEFAULT_OPEN_TIMEOUT;
-
-    /**
-     * Sets the tcpServer attribute of the ILateralCacheAttributes object
-     * <p>
-     * @param val The new tcpServer value
-     */
-    @Override
-    public void setTcpServer( String val )
-    {
-        this.tcpServer = val;
-    }
-
-    /**
-     * Gets the tcpServer attribute of the ILateralCacheAttributes object
-     * <p>
-     * @return The tcpServer value
-     */
-    @Override
-    public String getTcpServer()
-    {
-        return this.tcpServer;
-    }
-
-    /**
-     * Sets the tcpServers attribute of the ILateralCacheAttributes object
-     * <p>
-     * @param val The new tcpServers value
-     */
-    @Override
-    public void setTcpServers( String val )
-    {
-        this.tcpServers = val;
-    }
-
-    /**
-     * Gets the tcpServers attribute of the ILateralCacheAttributes object
-     * <p>
-     * @return The tcpServers value
-     */
-    @Override
-    public String getTcpServers()
-    {
-        return this.tcpServers;
-    }
-
-    /**
-     * Sets the tcpListenerPort attribute of the ILateralCacheAttributes object
-     * <p>
-     * @param val The new tcpListenerPort value
-     */
-    @Override
-    public void setTcpListenerPort( int val )
-    {
-        this.tcpListenerPort = val;
-    }
-
-    /**
-     * Gets the tcpListenerPort attribute of the ILateralCacheAttributes object
-     * <p>
-     * @return The tcpListenerPort value
-     */
-    @Override
-    public int getTcpListenerPort()
-    {
-        return this.tcpListenerPort;
-    }
-
-    /**
-     * Sets the tcpListenerHost attribute of the ILateralCacheAttributes object
-     * <p>
-     * @param val
-     *            The new tcpListenerHost value
-     */
-    @Override
-    public void setTcpListenerHost( String val )
-    {
-        this.tcpListenerHost = val;
-    }
-
-    /**
-     * Gets the tcpListenerHost attribute of the ILateralCacheAttributes object
-     * <p>
-     * @return The tcpListenerHost value
-     */
-    @Override
-    public String getTcpListenerHost()
-    {
-        return this.tcpListenerHost;
-    }
-
-    /**
-     * Can setup UDP Discovery. This only works for TCp laterals right now. It allows TCP laterals
-     * to find each other by broadcasting to a multicast port.
-     * <p>
-     * @param udpDiscoveryEnabled The udpDiscoveryEnabled to set.
-     */
-    @Override
-    public void setUdpDiscoveryEnabled( boolean udpDiscoveryEnabled )
-    {
-        this.udpDiscoveryEnabled = udpDiscoveryEnabled;
-    }
-
-    /**
-     * Whether or not TCP laterals can try to find each other by multicast communication.
-     * <p>
-     * @return Returns the udpDiscoveryEnabled.
-     */
-    @Override
-    public boolean isUdpDiscoveryEnabled()
-    {
-        return this.udpDiscoveryEnabled;
-    }
-
-    /**
-     * The port to use if UDPDiscovery is enabled.
-     * <p>
-     * @return Returns the udpDiscoveryPort.
-     */
-    @Override
-    public int getUdpDiscoveryPort()
-    {
-        return this.udpDiscoveryPort;
-    }
-
-    /**
-     * Sets the port to use if UDPDiscovery is enabled.
-     * <p>
-     * @param udpDiscoveryPort The udpDiscoveryPort to set.
-     */
-    @Override
-    public void setUdpDiscoveryPort( int udpDiscoveryPort )
-    {
-        this.udpDiscoveryPort = udpDiscoveryPort;
-    }
-
-    /**
-     * The address to broadcast to if UDPDiscovery is enabled.
-     * <p>
-     * @return Returns the udpDiscoveryAddr.
-     */
-    @Override
-    public String getUdpDiscoveryAddr()
-    {
-        return this.udpDiscoveryAddr;
-    }
-
-    /**
-     * Sets the address to broadcast to if UDPDiscovery is enabled.
-     * <p>
-     * @param udpDiscoveryAddr The udpDiscoveryAddr to set.
-     */
-    @Override
-    public void setUdpDiscoveryAddr( String udpDiscoveryAddr )
-    {
-        this.udpDiscoveryAddr = udpDiscoveryAddr;
-    }
-
-    /**
-     * Is the lateral allowed to try and get from other laterals.
-     * <p>
-     * This replaces the old putOnlyMode
-     * <p>
-     * @param allowGet
-     */
-    @Override
-    public void setAllowGet( boolean allowGet )
-    {
-        this.allowGet = allowGet;
-    }
-
-    /**
-     * Is the lateral allowed to try and get from other laterals.
-     * <p>
-     * @return true if the lateral will try to get
-     */
-    @Override
-    public boolean isAllowGet()
-    {
-        return this.allowGet;
-    }
-
-    /**
-     * Is the lateral allowed to put objects to other laterals.
-     * <p>
-     * @param allowPut
-     */
-    @Override
-    public void setAllowPut( boolean allowPut )
-    {
-        this.allowPut = allowPut;
-    }
-
-    /**
-     * Is the lateral allowed to put objects to other laterals.
-     * <p>
-     * @return true if puts are allowed
-     */
-    @Override
-    public boolean isAllowPut()
-    {
-        return this.allowPut;
-    }
-
-    /**
-     * Should the client send a remove command rather than a put when update is called. This is a
-     * client option, not a receiver option. This allows you to prevent the lateral from serializing
-     * objects.
-     * <p>
-     * @param issueRemoveOnPut
-     */
-    @Override
-    public void setIssueRemoveOnPut( boolean issueRemoveOnPut )
-    {
-        this.issueRemoveOnPut = issueRemoveOnPut;
-    }
-
-    /**
-     * Should the client send a remove command rather than a put when update is called. This is a
-     * client option, not a receiver option. This allows you to prevent the lateral from serializing
-     * objects.
-     * <p>
-     * @return true if updates will result in a remove command being sent.
-     */
-    @Override
-    public boolean isIssueRemoveOnPut()
-    {
-        return this.issueRemoveOnPut;
-    }
-
-    /**
-     * Should the receiver try to match hashcodes. If true, the receiver will see if the client
-     * supplied a hashcode. If it did, then it will try to get the item locally. If the item exists,
-     * then it will compare the hashcode. if they are the same, it will not remove. This isn't
-     * perfect since different objects can have the same hashcode, but it is unlikely of objects of
-     * the same type.
-     * <p>
-     * @return boolean
-     */
-    @Override
-    public boolean isFilterRemoveByHashCode()
-    {
-        return this.filterRemoveByHashCode;
-    }
-
-    /**
-     * Should the receiver try to match hashcodes. If true, the receiver will see if the client
-     * supplied a hashcode. If it did, then it will try to get the item locally. If the item exists,
-     * then it will compare the hashcode. if they are the same, it will not remove. This isn't
-     * perfect since different objects can have the same hashcode, but it is unlikely of objects of
-     * the same type.
-     * <p>
-     * @param filter
-     */
-    @Override
-    public void setFilterRemoveByHashCode( boolean filter )
-    {
-        this.filterRemoveByHashCode = filter;
-    }
-
-    /**
-     * @param socketTimeOut the socketTimeOut to set
-     */
-    @Override
-    public void setSocketTimeOut( int socketTimeOut )
-    {
-        this.socketTimeOut = socketTimeOut;
-    }
-
-    /**
-     * @return the socketTimeOut
-     */
-    @Override
-    public int getSocketTimeOut()
-    {
-        return socketTimeOut;
-    }
-
-    /**
-     * @param openTimeOut the openTimeOut to set
-     */
-    @Override
-    public void setOpenTimeOut( int openTimeOut )
-    {
-        this.openTimeOut = openTimeOut;
-    }
-
-    /**
-     * @return the openTimeOut
-     */
-    @Override
-    public int getOpenTimeOut()
-    {
-        return openTimeOut;
-    }
-
-    /**
-     * Used to key the instance TODO create another method for this and use toString for debugging
-     * only.
-     * <p>
-     * @return String
-     */
-    @Override
-    public String toString()
-    {
-        return this.getTcpServer() + ":" + this.getTcpListenerPort();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/behavior/ITCPLateralCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/behavior/ITCPLateralCacheAttributes.java
deleted file mode 100644
index 7ffd3bc..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/behavior/ITCPLateralCacheAttributes.java
+++ /dev/null
@@ -1,233 +0,0 @@
-package org.apache.commons.jcs.auxiliary.lateral.socket.tcp.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.lateral.behavior.ILateralCacheAttributes;
-
-/**
- * This interface defines functions that are particular to the TCP Lateral Cache
- * plugin. It extends the generic LateralCacheAttributes interface which in turn
- * extends the AuxiliaryCache interface.
- * <p>
- * @author Aaron Smuts
- */
-public interface ITCPLateralCacheAttributes
-    extends ILateralCacheAttributes
-{
-    /**
-     * Sets the tcpServer attribute of the ILateralCacheAttributes object
-     * <p>
-     * @param val
-     *            The new tcpServer value
-     */
-    void setTcpServer( String val );
-
-    /**
-     * Gets the tcpServer attribute of the ILateralCacheAttributes object
-     * <p>
-     * @return The tcpServer value
-     */
-    String getTcpServer();
-
-    /**
-     * Sets the tcpServers attribute of the ILateralCacheAttributes object
-     * <p>
-     * @param val
-     *            The new tcpServers value
-     */
-    void setTcpServers( String val );
-
-    /**
-     * Gets the tcpServers attribute of the ILateralCacheAttributes object
-     * <p>
-     * @return The tcpServers value
-     */
-    String getTcpServers();
-
-    /**
-     * Sets the tcpListenerPort attribute of the ILateralCacheAttributes object
-     * <p>
-     * @param val
-     *            The new tcpListenerPort value
-     */
-    void setTcpListenerPort( int val );
-
-    /**
-     * Gets the tcpListenerPort attribute of the ILateralCacheAttributes object
-     * <p>
-     * @return The tcpListenerPort value
-     */
-    int getTcpListenerPort();
-
-    /**
-     * Sets the tcpListenerHost attribute of the ILateralCacheAttributes object
-     * <p>
-     * @param val
-     *            The new tcpListenerHost value
-     */
-    void setTcpListenerHost( String val );
-
-    /**
-     * Gets the tcpListenerHost attribute of the ILateralCacheAttributes object
-     * <p>
-     * @return The tcpListenerHost value
-     */
-    String getTcpListenerHost();
-
-    /**
-     * Can setup UDP Discovery. This only works for TCp laterals right now. It
-     * allows TCP laterals to find each other by broadcasting to a multicast
-     * port.
-     * <p>
-     * @param udpDiscoveryEnabled
-     *            The udpDiscoveryEnabled to set.
-     */
-    void setUdpDiscoveryEnabled( boolean udpDiscoveryEnabled );
-
-    /**
-     * Whether or not TCP laterals can try to find each other by multicast
-     * communication.
-     * <p>
-     * @return Returns the udpDiscoveryEnabled.
-     */
-    boolean isUdpDiscoveryEnabled();
-
-    /**
-     * The port to use if UDPDiscovery is enabled.
-     * <p>
-     * @return Returns the udpDiscoveryPort.
-     */
-    int getUdpDiscoveryPort();
-
-    /**
-     * Sets the port to use if UDPDiscovery is enabled.
-     * <p>
-     * @param udpDiscoveryPort
-     *            The udpDiscoveryPort to set.
-     */
-    void setUdpDiscoveryPort( int udpDiscoveryPort );
-
-    /**
-     * The address to broadcast to if UDPDiscovery is enabled.
-     * <p>
-     * @return Returns the udpDiscoveryAddr.
-     */
-    String getUdpDiscoveryAddr();
-
-    /**
-     * Sets the address to broadcast to if UDPDiscovery is enabled.
-     * <p>
-     * @param udpDiscoveryAddr
-     *            The udpDiscoveryAddr to set.
-     */
-    void setUdpDiscoveryAddr( String udpDiscoveryAddr );
-
-    /**
-     * Is the lateral allowed to try and get from other laterals.
-     * <p>
-     * This replaces the old putOnlyMode
-     * <p>
-     * @param allowGet
-     */
-    void setAllowGet( boolean allowGet );
-
-    /**
-     * Is the lateral allowed to try and get from other laterals.
-     * <p>
-     * @return true if the lateral will try to get
-     */
-    boolean isAllowGet();
-
-    /**
-     * Is the lateral allowed to put objects to other laterals.
-     * <p>
-     * @param allowPut
-     */
-    void setAllowPut( boolean allowPut );
-
-    /**
-     * Is the lateral allowed to put objects to other laterals.
-     * <p>
-     * @return true if puts are allowed
-     */
-    boolean isAllowPut();
-
-    /**
-     * Should the client send a remove command rather than a put when update is
-     * called. This is a client option, not a receiver option. This allows you
-     * to prevent the lateral from serializing objects.
-     * <p>
-     * @param issueRemoveOnPut
-     */
-    void setIssueRemoveOnPut( boolean issueRemoveOnPut );
-
-    /**
-     * Should the client send a remove command rather than a put when update is
-     * called. This is a client option, not a receiver option. This allows you
-     * to prevent the lateral from serializing objects.
-     * <p>
-     * @return true if updates will result in a remove command being sent.
-     */
-    boolean isIssueRemoveOnPut();
-
-    /**
-     * Should the receiver try to match hashcodes. If true, the receiver will
-     * see if the client supplied a hashcode. If it did, then it will try to get
-     * the item locally. If the item exists, then it will compare the hashcode.
-     * if they are the same, it will not remove. This isn't perfect since
-     * different objects can have the same hashcode, but it is unlikely of
-     * objects of the same type.
-     * <p>
-     * @return boolean
-     */
-    boolean isFilterRemoveByHashCode();
-
-    /**
-     * Should the receiver try to match hashcodes. If true, the receiver will
-     * see if the client supplied a hashcode. If it did, then it will try to get
-     * the item locally. If the item exists, then it will compare the hashcode.
-     * if they are the same, it will not remove. This isn't perfect since
-     * different objects can have the same hashcode, but it is unlikely of
-     * objects of the same type.
-     * <p>
-     * @param filter
-     */
-    void setFilterRemoveByHashCode( boolean filter );
-
-    /**
-     * @param socketTimeOut the socketTimeOut to set
-     */
-    void setSocketTimeOut( int socketTimeOut );
-
-    /**
-     * @return the socketTimeOut
-     */
-    int getSocketTimeOut();
-
-    /**
-     * @param openTimeOut the openTimeOut to set
-     */
-    void setOpenTimeOut( int openTimeOut );
-
-    /**
-     * @return the openTimeOut
-     */
-    int getOpenTimeOut();
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/AbstractRemoteAuxiliaryCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/AbstractRemoteAuxiliaryCache.java
deleted file mode 100644
index f599a43..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/AbstractRemoteAuxiliaryCache.java
+++ /dev/null
@@ -1,661 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-import org.apache.commons.jcs.auxiliary.AbstractAuxiliaryCacheEventLogging;
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes;
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheAttributes;
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheClient;
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheListener;
-import org.apache.commons.jcs.auxiliary.remote.server.behavior.RemoteType;
-import org.apache.commons.jcs.engine.CacheStatus;
-import org.apache.commons.jcs.engine.ZombieCacheServiceNonLocal;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheElementSerialized;
-import org.apache.commons.jcs.engine.behavior.ICacheServiceNonLocal;
-import org.apache.commons.jcs.engine.behavior.IZombie;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-import org.apache.commons.jcs.engine.stats.StatElement;
-import org.apache.commons.jcs.engine.stats.Stats;
-import org.apache.commons.jcs.engine.stats.behavior.IStatElement;
-import org.apache.commons.jcs.engine.stats.behavior.IStats;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-import org.apache.commons.jcs.utils.serialization.SerializationConversionUtil;
-import org.apache.commons.jcs.utils.threadpool.ThreadPoolManager;
-
-/** Abstract base for remote caches. I'm trying to break out and reuse common functionality. */
-public abstract class AbstractRemoteAuxiliaryCache<K, V>
-    extends AbstractAuxiliaryCacheEventLogging<K, V>
-    implements IRemoteCacheClient<K, V>
-{
-    /** The logger. */
-    private static final Log log = LogManager.getLog( AbstractRemoteAuxiliaryCache.class );
-
-    /**
-     * This does the work. In an RMI instances, it will be a remote reference. In an http remote
-     * cache it will be an http client. In zombie mode it is replaced with a balking facade.
-     */
-    private ICacheServiceNonLocal<K, V> remoteCacheService;
-
-    /** The cacheName */
-    protected final String cacheName;
-
-    /** The listener. This can be null. */
-    private IRemoteCacheListener<K, V> remoteCacheListener;
-
-    /** The configuration values. TODO, we'll need a base here. */
-    private IRemoteCacheAttributes remoteCacheAttributes;
-
-    /** A thread pool for gets if configured. */
-    private ExecutorService pool = null;
-
-    /** Should we get asynchronously using a pool. */
-    private boolean usePoolForGet = false;
-
-    /**
-     * Creates the base.
-     * <p>
-     * @param cattr
-     * @param remote
-     * @param listener
-     */
-    public AbstractRemoteAuxiliaryCache( IRemoteCacheAttributes cattr, ICacheServiceNonLocal<K, V> remote,
-                                         IRemoteCacheListener<K, V> listener )
-    {
-        this.setRemoteCacheAttributes( cattr );
-        this.cacheName = cattr.getCacheName();
-        this.setRemoteCacheService( remote );
-        this.setRemoteCacheListener( listener );
-
-        if ( log.isDebugEnabled() )
-        {
-            log.debug( "Construct> cacheName={0}", () -> cattr.getCacheName() );
-            log.debug( "irca = {0}", () -> getRemoteCacheAttributes() );
-            log.debug( "remote = {0}", remote );
-            log.debug( "listener = {0}", listener );
-        }
-
-        // use a pool if it is greater than 0
-        log.debug( "GetTimeoutMillis() = {0}",
-                () -> getRemoteCacheAttributes().getGetTimeoutMillis() );
-
-        if ( getRemoteCacheAttributes().getGetTimeoutMillis() > 0 )
-        {
-            pool = ThreadPoolManager.getInstance().getExecutorService( getRemoteCacheAttributes().getThreadPoolName() );
-            log.debug( "Thread Pool = {0}", pool );
-            usePoolForGet = true;
-        }
-    }
-
-    /**
-     * Synchronously dispose the remote cache; if failed, replace the remote handle with a zombie.
-     * <p>
-     * @throws IOException
-     */
-    @Override
-    protected void processDispose()
-        throws IOException
-    {
-        log.info( "Disposing of remote cache." );
-        try
-        {
-            if ( getRemoteCacheListener() != null )
-            {
-                getRemoteCacheListener().dispose();
-            }
-        }
-        catch ( IOException ex )
-        {
-            log.error( "Couldn't dispose", ex );
-            handleException( ex, "Failed to dispose [" + cacheName + "]", ICacheEventLogger.DISPOSE_EVENT );
-        }
-    }
-
-    /**
-     * Synchronously get from the remote cache; if failed, replace the remote handle with a zombie.
-     * <p>
-     * Use threadpool to timeout if a value is set for GetTimeoutMillis
-     * <p>
-     * If we are a cluster client, we need to leave the Element in its serialized form. Cluster
-     * clients cannot deserialize objects. Cluster clients get ICacheElementSerialized objects from
-     * other remote servers.
-     * <p>
-     * @param key
-     * @return ICacheElement, a wrapper around the key, value, and attributes
-     * @throws IOException
-     */
-    @Override
-    protected ICacheElement<K, V> processGet( K key )
-        throws IOException
-    {
-        ICacheElement<K, V> retVal = null;
-        try
-        {
-            if ( usePoolForGet )
-            {
-                retVal = getUsingPool( key );
-            }
-            else
-            {
-                retVal = getRemoteCacheService().get( cacheName, key, getListenerId() );
-            }
-
-            // Eventually the instance of will not be necessary.
-            if ( retVal instanceof ICacheElementSerialized )
-            {
-                // Never try to deserialize if you are a cluster client. Cluster
-                // clients are merely intra-remote cache communicators. Remote caches are assumed
-                // to have no ability to deserialize the objects.
-                if ( this.getRemoteCacheAttributes().getRemoteType() != RemoteType.CLUSTER )
-                {
-                    retVal = SerializationConversionUtil.getDeSerializedCacheElement( (ICacheElementSerialized<K, V>) retVal,
-                            super.getElementSerializer() );
-                }
-            }
-        }
-        catch ( Exception ex )
-        {
-            handleException( ex, "Failed to get [" + key + "] from [" + cacheName + "]", ICacheEventLogger.GET_EVENT );
-        }
-        return retVal;
-    }
-
-    /**
-     * This allows gets to timeout in case of remote server machine shutdown.
-     * <p>
-     * @param key
-     * @return ICacheElement
-     * @throws IOException
-     */
-    public ICacheElement<K, V> getUsingPool( final K key )
-        throws IOException
-    {
-        int timeout = getRemoteCacheAttributes().getGetTimeoutMillis();
-
-        try
-        {
-            Callable<ICacheElement<K, V>> command = new Callable<ICacheElement<K, V>>()
-            {
-                @Override
-                public ICacheElement<K, V> call()
-                    throws IOException
-                {
-                    return getRemoteCacheService().get( cacheName, key, getListenerId() );
-                }
-            };
-
-            // execute using the pool
-            Future<ICacheElement<K, V>> future = pool.submit(command);
-
-            // used timed get in order to timeout
-            ICacheElement<K, V> ice = future.get(timeout, TimeUnit.MILLISECONDS);
-
-            if ( ice == null )
-            {
-                log.debug( "nothing found in remote cache" );
-            }
-            else
-            {
-                log.debug( "found item in remote cache" );
-            }
-            return ice;
-        }
-        catch ( TimeoutException te )
-        {
-            log.warn( "TimeoutException, Get Request timed out after {0}", timeout );
-            throw new IOException( "Get Request timed out after " + timeout );
-        }
-        catch ( InterruptedException ex )
-        {
-            log.warn( "InterruptedException, Get Request timed out after {0}", timeout );
-            throw new IOException( "Get Request timed out after " + timeout );
-        }
-        catch (ExecutionException ex)
-        {
-            // assume that this is an IOException thrown by the callable.
-            log.error( "ExecutionException, Assuming an IO exception thrown in the background.", ex );
-            throw new IOException( "Get Request timed out after " + timeout );
-        }
-    }
-
-    /**
-     * Calls get matching on the server. Each entry in the result is unwrapped.
-     * <p>
-     * @param pattern
-     * @return Map
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> processGetMatching( String pattern )
-        throws IOException
-    {
-        Map<K, ICacheElement<K, V>> results = new HashMap<>();
-        try
-        {
-            Map<K, ICacheElement<K, V>> rawResults = getRemoteCacheService().getMatching( cacheName, pattern, getListenerId() );
-
-            // Eventually the instance of will not be necessary.
-            if ( rawResults != null )
-            {
-                for (Map.Entry<K, ICacheElement<K, V>> entry : rawResults.entrySet())
-                {
-                    ICacheElement<K, V> unwrappedResult = null;
-                    if ( entry.getValue() instanceof ICacheElementSerialized )
-                    {
-                        // Never try to deserialize if you are a cluster client. Cluster
-                        // clients are merely intra-remote cache communicators. Remote caches are assumed
-                        // to have no ability to deserialize the objects.
-                        if ( this.getRemoteCacheAttributes().getRemoteType() != RemoteType.CLUSTER )
-                        {
-                            unwrappedResult = SerializationConversionUtil
-                                .getDeSerializedCacheElement( (ICacheElementSerialized<K, V>) entry.getValue(),
-                                        super.getElementSerializer() );
-                        }
-                    }
-                    else
-                    {
-                        unwrappedResult = entry.getValue();
-                    }
-                    results.put( entry.getKey(), unwrappedResult );
-                }
-            }
-        }
-        catch ( Exception ex )
-        {
-            handleException( ex, "Failed to getMatching [" + pattern + "] from [" + cacheName + "]",
-                             ICacheEventLogger.GET_EVENT );
-        }
-        return results;
-    }
-
-    /**
-     * Synchronously remove from the remote cache; if failed, replace the remote handle with a
-     * zombie.
-     * <p>
-     * @param key
-     * @return boolean, whether or not the item was removed
-     * @throws IOException
-     */
-    @Override
-    protected boolean processRemove( K key )
-        throws IOException
-    {
-        if ( !this.getRemoteCacheAttributes().getGetOnly() )
-        {
-            log.debug( "remove> key={0}", key );
-            try
-            {
-                getRemoteCacheService().remove( cacheName, key, getListenerId() );
-            }
-            catch ( Exception ex )
-            {
-                handleException( ex, "Failed to remove " + key + " from " + cacheName, ICacheEventLogger.REMOVE_EVENT );
-            }
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Synchronously removeAll from the remote cache; if failed, replace the remote handle with a
-     * zombie.
-     * <p>
-     * @throws IOException
-     */
-    @Override
-    protected void processRemoveAll()
-        throws IOException
-    {
-        if ( !this.getRemoteCacheAttributes().getGetOnly() )
-        {
-            try
-            {
-                getRemoteCacheService().removeAll( cacheName, getListenerId() );
-            }
-            catch ( Exception ex )
-            {
-                handleException( ex, "Failed to remove all from " + cacheName, ICacheEventLogger.REMOVEALL_EVENT );
-            }
-        }
-    }
-
-    /**
-     * Serializes the object and then calls update on the remote server with the byte array. The
-     * byte array is wrapped in a ICacheElementSerialized. This allows the remote server to operate
-     * without any knowledge of caches classes.
-     * <p>
-     * @param ce
-     * @throws IOException
-     */
-    @Override
-    protected void processUpdate( ICacheElement<K, V> ce )
-        throws IOException
-    {
-        if ( !getRemoteCacheAttributes().getGetOnly() )
-        {
-            ICacheElementSerialized<K, V> serialized = null;
-            try
-            {
-                log.debug( "sending item to remote server" );
-
-                // convert so we don't have to know about the object on the
-                // other end.
-                serialized = SerializationConversionUtil.getSerializedCacheElement( ce, super.getElementSerializer() );
-
-                remoteCacheService.update( serialized, getListenerId() );
-            }
-            catch ( NullPointerException npe )
-            {
-                log.error( "npe for ce = {0} ce.attr = {1}", ce, ce.getElementAttributes(), npe );
-            }
-            catch ( Exception ex )
-            {
-                // event queue will wait and retry
-                handleException( ex, "Failed to put [" + ce.getKey() + "] to " + ce.getCacheName(),
-                                 ICacheEventLogger.UPDATE_EVENT );
-            }
-        }
-        else
-        {
-            log.debug( "get only mode, not sending to remote server" );
-        }
-    }
-
-    /**
-     * Return the keys in this cache.
-     * <p>
-     * @see org.apache.commons.jcs.auxiliary.AuxiliaryCache#getKeySet()
-     */
-    @Override
-    public Set<K> getKeySet()
-        throws IOException
-    {
-        return getRemoteCacheService().getKeySet(cacheName);
-    }
-
-    /**
-     * Allows other member of this package to access the listener. This is mainly needed for
-     * deregistering a listener.
-     * <p>
-     * @return IRemoteCacheListener, the listener for this remote server
-     */
-    @Override
-    public IRemoteCacheListener<K, V> getListener()
-    {
-        return getRemoteCacheListener();
-    }
-
-    /**
-     * let the remote cache set a listener_id. Since there is only one listener for all the regions
-     * and every region gets registered? the id shouldn't be set if it isn't zero. If it is we
-     * assume that it is a reconnect.
-     * <p>
-     * @param id The new listenerId value
-     */
-    public void setListenerId( long id )
-    {
-        if ( getRemoteCacheListener() != null )
-        {
-            try
-            {
-                getRemoteCacheListener().setListenerId( id );
-
-                log.debug( "set listenerId = {0}", id );
-            }
-            catch ( Exception e )
-            {
-                log.error( "Problem setting listenerId", e );
-            }
-        }
-    }
-
-    /**
-     * Gets the listenerId attribute of the RemoteCacheListener object
-     * <p>
-     * @return The listenerId value
-     */
-    @Override
-    public long getListenerId()
-    {
-        if ( getRemoteCacheListener() != null )
-        {
-            try
-            {
-                log.debug( "get listenerId = {0}", getRemoteCacheListener().getListenerId() );
-                return getRemoteCacheListener().getListenerId();
-            }
-            catch ( IOException e )
-            {
-                log.error( "Problem getting listenerId", e );
-            }
-        }
-        return -1;
-    }
-
-    /**
-     * Returns the current cache size.
-     * @return The size value
-     */
-    @Override
-    public int getSize()
-    {
-        return 0;
-    }
-
-    /**
-     * Custom exception handling some children.  This should be used to initiate failover.
-     * <p>
-     * @param ex
-     * @param msg
-     * @param eventName
-     * @throws IOException
-     */
-    protected abstract void handleException( Exception ex, String msg, String eventName )
-        throws IOException;
-
-    /**
-     * Gets the stats attribute of the RemoteCache object.
-     * <p>
-     * @return The stats value
-     */
-    @Override
-    public String getStats()
-    {
-        return getStatistics().toString();
-    }
-
-    /**
-     * @return IStats object
-     */
-    @Override
-    public IStats getStatistics()
-    {
-        IStats stats = new Stats();
-        stats.setTypeName( "AbstractRemoteAuxiliaryCache" );
-
-        ArrayList<IStatElement<?>> elems = new ArrayList<>();
-
-        elems.add(new StatElement<>( "Remote Type", this.getRemoteCacheAttributes().getRemoteTypeName() ) );
-
-//      if ( this.getRemoteCacheAttributes().getRemoteType() == RemoteType.CLUSTER )
-//      {
-//          // something cluster specific
-//      }
-
-        elems.add(new StatElement<>( "UsePoolForGet", Boolean.valueOf(usePoolForGet) ) );
-
-        if ( pool != null )
-        {
-            elems.add(new StatElement<>( "Pool", pool ) );
-        }
-
-        if ( getRemoteCacheService() instanceof ZombieCacheServiceNonLocal )
-        {
-            elems.add(new StatElement<>( "Zombie Queue Size",
-                    Integer.valueOf(( (ZombieCacheServiceNonLocal<K, V>) getRemoteCacheService() ).getQueueSize()) ) );
-        }
-
-        stats.setStatElements( elems );
-
-        return stats;
-    }
-
-    /**
-     * Returns the cache status. An error status indicates the remote connection is not available.
-     * <p>
-     * @return The status value
-     */
-    @Override
-    public CacheStatus getStatus()
-    {
-        return getRemoteCacheService() instanceof IZombie ? CacheStatus.ERROR : CacheStatus.ALIVE;
-    }
-
-    /**
-     * Replaces the current remote cache service handle with the given handle. If the current remote
-     * is a Zombie, then it propagates any events that are queued to the restored service.
-     * <p>
-     * @param restoredRemote ICacheServiceNonLocal -- the remote server or proxy to the remote server
-     */
-    @Override
-    public void fixCache( ICacheServiceNonLocal<?, ?> restoredRemote )
-    {
-        @SuppressWarnings("unchecked") // Don't know how to do this properly
-        ICacheServiceNonLocal<K, V> remote = (ICacheServiceNonLocal<K, V>)restoredRemote;
-        ICacheServiceNonLocal<K, V> prevRemote = getRemoteCacheService();
-        if ( prevRemote instanceof ZombieCacheServiceNonLocal )
-        {
-            ZombieCacheServiceNonLocal<K, V> zombie = (ZombieCacheServiceNonLocal<K, V>) prevRemote;
-            setRemoteCacheService( remote );
-            try
-            {
-                zombie.propagateEvents( remote );
-            }
-            catch ( Exception e )
-            {
-                try
-                {
-                    handleException( e, "Problem propagating events from Zombie Queue to new Remote Service.",
-                                     "fixCache" );
-                }
-                catch ( IOException e1 )
-                {
-                    // swallow, since this is just expected kick back.  Handle always throws
-                }
-            }
-        }
-        else
-        {
-            setRemoteCacheService( remote );
-        }
-    }
-
-
-    /**
-     * Gets the cacheType attribute of the RemoteCache object
-     * @return The cacheType value
-     */
-    @Override
-    public CacheType getCacheType()
-    {
-        return CacheType.REMOTE_CACHE;
-    }
-
-    /**
-     * Gets the cacheName attribute of the RemoteCache object.
-     * <p>
-     * @return The cacheName value
-     */
-    @Override
-    public String getCacheName()
-    {
-        return cacheName;
-    }
-
-    /**
-     * @param remote the remote to set
-     */
-    protected void setRemoteCacheService( ICacheServiceNonLocal<K, V> remote )
-    {
-        this.remoteCacheService = remote;
-    }
-
-    /**
-     * @return the remote
-     */
-    protected ICacheServiceNonLocal<K, V> getRemoteCacheService()
-    {
-        return remoteCacheService;
-    }
-
-    /**
-     * @return Returns the AuxiliaryCacheAttributes.
-     */
-    @Override
-    public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
-    {
-        return getRemoteCacheAttributes();
-    }
-
-    /**
-     * @param remoteCacheAttributes the remoteCacheAttributes to set
-     */
-    protected void setRemoteCacheAttributes( IRemoteCacheAttributes remoteCacheAttributes )
-    {
-        this.remoteCacheAttributes = remoteCacheAttributes;
-    }
-
-    /**
-     * @return the remoteCacheAttributes
-     */
-    protected IRemoteCacheAttributes getRemoteCacheAttributes()
-    {
-        return remoteCacheAttributes;
-    }
-
-    /**
-     * @param remoteCacheListener the remoteCacheListener to set
-     */
-    protected void setRemoteCacheListener( IRemoteCacheListener<K, V> remoteCacheListener )
-    {
-        this.remoteCacheListener = remoteCacheListener;
-    }
-
-    /**
-     * @return the remoteCacheListener
-     */
-    protected IRemoteCacheListener<K, V> getRemoteCacheListener()
-    {
-        return remoteCacheListener;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/AbstractRemoteCacheListener.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/AbstractRemoteCacheListener.java
deleted file mode 100644
index eafbc54..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/AbstractRemoteCacheListener.java
+++ /dev/null
@@ -1,266 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.net.UnknownHostException;
-
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheAttributes;
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheListener;
-import org.apache.commons.jcs.auxiliary.remote.server.behavior.RemoteType;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheElementSerialized;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-import org.apache.commons.jcs.utils.net.HostNameUtil;
-import org.apache.commons.jcs.utils.serialization.SerializationConversionUtil;
-
-/** Shared listener base. */
-public abstract class AbstractRemoteCacheListener<K, V>
-    implements IRemoteCacheListener<K, V>
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog( AbstractRemoteCacheListener.class );
-
-    /** The cached name of the local host. The remote server gets this for logging purposes. */
-    private static String localHostName = null;
-
-    /**
-     * The cache manager used to put items in different regions. This is set lazily and should not
-     * be sent to the remote server.
-     */
-    private ICompositeCacheManager cacheMgr;
-
-    /** The remote cache configuration object. */
-    private final IRemoteCacheAttributes irca;
-
-    /** This is set by the remote cache server. */
-    private long listenerId = 0;
-
-    /** Custom serializer. */
-    private IElementSerializer elementSerializer;
-
-    /**
-     * Only need one since it does work for all regions, just reference by multiple region names.
-     * <p>
-     * The constructor exports this object, making it available to receive incoming calls. The
-     * callback port is anonymous unless a local port value was specified in the configuration.
-     * <p>
-     * @param irca cache configuration
-     * @param cacheMgr the cache hub
-     * @param elementSerializer a custom serializer
-     */
-    public AbstractRemoteCacheListener( IRemoteCacheAttributes irca, ICompositeCacheManager cacheMgr, IElementSerializer elementSerializer )
-    {
-        this.irca = irca;
-        this.cacheMgr = cacheMgr;
-        this.elementSerializer = elementSerializer;
-    }
-
-    /**
-     * Let the remote cache set a listener_id. Since there is only one listener for all the regions
-     * and every region gets registered? the id shouldn't be set if it isn't zero. If it is we
-     * assume that it is a reconnect.
-     * <p>
-     * @param id The new listenerId value
-     * @throws IOException
-     */
-    @Override
-    public void setListenerId( long id )
-        throws IOException
-    {
-        listenerId = id;
-        log.info( "set listenerId = [{0}]", id );
-    }
-
-    /**
-     * Gets the listenerId attribute of the RemoteCacheListener object. This is stored in the
-     * object. The RemoteCache object contains a reference to the listener and get the id this way.
-     * <p>
-     * @return The listenerId value
-     * @throws IOException
-     */
-    @Override
-    public long getListenerId()
-        throws IOException
-    {
-        log.debug( "get listenerId = [{0}]", listenerId );
-        return listenerId;
-
-    }
-
-    /**
-     * Gets the remoteType attribute of the RemoteCacheListener object
-     * <p>
-     * @return The remoteType value
-     * @throws IOException
-     */
-    @Override
-    public RemoteType getRemoteType()
-        throws IOException
-    {
-        log.debug( "getRemoteType = [{0}]", () -> irca.getRemoteType() );
-        return irca.getRemoteType();
-    }
-
-    /**
-     * If this is configured to remove on put, then remove the element since it has been updated
-     * elsewhere. cd should be incomplete for faster transmission. We don't want to pass data only
-     * invalidation. The next time it is used the local cache will get the new version from the
-     * remote store.
-     * <p>
-     * If remove on put is not configured, then update the item.
-     * @param cb
-     * @throws IOException
-     */
-    @Override
-    public void handlePut( ICacheElement<K, V> cb )
-        throws IOException
-    {
-        if ( irca.getRemoveUponRemotePut() )
-        {
-            log.debug( "PUTTING ELEMENT FROM REMOTE, (  invalidating ) " );
-            handleRemove( cb.getCacheName(), cb.getKey() );
-        }
-        else
-        {
-            log.debug( "PUTTING ELEMENT FROM REMOTE, ( updating ) " );
-            log.debug( "cb = {0}", cb );
-
-            // Eventually the instance of will not be necessary.
-            if ( cb instanceof ICacheElementSerialized )
-            {
-                log.debug( "Object needs to be deserialized." );
-                try
-                {
-                    cb = SerializationConversionUtil.getDeSerializedCacheElement(
-                            (ICacheElementSerialized<K, V>) cb, this.elementSerializer );
-                    log.debug( "Deserialized result = {0}", cb );
-                }
-                catch ( IOException e )
-                {
-                    throw e;
-                }
-                catch ( ClassNotFoundException e )
-                {
-                    log.error( "Received a serialized version of a class that we don't know about.", e );
-                }
-            }
-
-            getCacheManager().<K, V>getCache( cb.getCacheName() ).localUpdate( cb );
-        }
-    }
-
-    /**
-     * Calls localRemove on the CompositeCache.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @throws IOException
-     */
-    @Override
-    public void handleRemove( String cacheName, K key )
-        throws IOException
-    {
-        log.debug( "handleRemove> cacheName={0}, key={1}", cacheName, key );
-
-        getCacheManager().<K, V>getCache( cacheName ).localRemove( key );
-    }
-
-    /**
-     * Calls localRemoveAll on the CompositeCache.
-     * <p>
-     * @param cacheName
-     * @throws IOException
-     */
-    @Override
-    public void handleRemoveAll( String cacheName )
-        throws IOException
-    {
-        log.debug( "handleRemoveAll> cacheName={0}", cacheName );
-
-        getCacheManager().<K, V>getCache( cacheName ).localRemoveAll();
-    }
-
-    /**
-     * @param cacheName
-     * @throws IOException
-     */
-    @Override
-    public void handleDispose( String cacheName )
-        throws IOException
-    {
-        log.debug( "handleDispose> cacheName={0}", cacheName );
-        // TODO consider what to do here, we really don't want to
-        // dispose, we just want to disconnect.
-        // just allow the cache to go into error recovery mode.
-        // getCacheManager().freeCache( cacheName, true );
-    }
-
-    /**
-     * Gets the cacheManager attribute of the RemoteCacheListener object. This is one of the few
-     * places that force the cache to be a singleton.
-     */
-    protected ICompositeCacheManager getCacheManager()
-    {
-        return cacheMgr;
-    }
-
-    /**
-     * This is for debugging. It allows the remote server to log the address of clients.
-     * <p>
-     * @return String
-     * @throws IOException
-     */
-    @Override
-    public synchronized String getLocalHostAddress()
-        throws IOException
-    {
-        if ( localHostName == null )
-        {
-            try
-            {
-                localHostName = HostNameUtil.getLocalHostAddress();
-            }
-            catch ( UnknownHostException uhe )
-            {
-                localHostName = "unknown";
-            }
-        }
-        return localHostName;
-    }
-
-    /**
-     * For easier debugging.
-     * <p>
-     * @return Basic info on this listener.
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder buf = new StringBuilder();
-        buf.append( "\n AbstractRemoteCacheListener: " )
-           .append( "\n RemoteHost = ").append(irca.getRemoteLocation())
-           .append( "\n ListenerId = ").append(listenerId);
-        return buf.toString();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/AbstractRemoteCacheNoWaitFacade.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/AbstractRemoteCacheNoWaitFacade.java
deleted file mode 100644
index 3c9650f..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/AbstractRemoteCacheNoWaitFacade.java
+++ /dev/null
@@ -1,434 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.commons.jcs.auxiliary.AbstractAuxiliaryCache;
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheAttributes;
-import org.apache.commons.jcs.engine.CacheStatus;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-import org.apache.commons.jcs.engine.stats.StatElement;
-import org.apache.commons.jcs.engine.stats.Stats;
-import org.apache.commons.jcs.engine.stats.behavior.IStatElement;
-import org.apache.commons.jcs.engine.stats.behavior.IStats;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/** An abstract base for the No Wait Facade.  Different implementations will failover differently. */
-public abstract class AbstractRemoteCacheNoWaitFacade<K, V>
-    extends AbstractAuxiliaryCache<K, V>
-{
-    /** log instance */
-    private static final Log log = LogManager.getLog( AbstractRemoteCacheNoWaitFacade.class );
-
-    /** The connection to a remote server, or a zombie. */
-    protected List<RemoteCacheNoWait<K, V>> noWaits;
-
-    /** holds failover and cluster information */
-    private IRemoteCacheAttributes remoteCacheAttributes;
-
-    /**
-     * Constructs with the given remote cache, and fires events to any listeners.
-     * <p>
-     * @param noWaits
-     * @param rca
-     * @param cacheEventLogger
-     * @param elementSerializer
-     */
-    public AbstractRemoteCacheNoWaitFacade( List<RemoteCacheNoWait<K,V>> noWaits, IRemoteCacheAttributes rca,
-                                    ICacheEventLogger cacheEventLogger, IElementSerializer elementSerializer )
-    {
-        log.debug( "CONSTRUCTING NO WAIT FACADE" );
-        this.remoteCacheAttributes = rca;
-        setCacheEventLogger( cacheEventLogger );
-        setElementSerializer( elementSerializer );
-        this.noWaits = new ArrayList<>(noWaits);
-        for (RemoteCacheNoWait<K,V> nw : this.noWaits)
-        {
-            // FIXME: This cast is very brave. Remove this.
-            ((RemoteCache<K, V>)nw.getRemoteCache()).setFacade(this);
-        }
-    }
-
-    /**
-     * Put an element in the cache.
-     * <p>
-     * @param ce
-     * @throws IOException
-     */
-    @Override
-    public void update( ICacheElement<K, V> ce )
-        throws IOException
-    {
-        log.debug( "updating through cache facade, noWaits.length = {0}",
-                () -> noWaits.size() );
-
-        for (RemoteCacheNoWait<K, V> nw : noWaits)
-        {
-            try
-            {
-                nw.update( ce );
-                // an initial move into a zombie will lock this to primary
-                // recovery. will not discover other servers until primary
-                // reconnect
-                // and subsequent error
-            }
-            catch ( IOException ex )
-            {
-                String message = "Problem updating no wait. Will initiate failover if the noWait is in error.";
-                log.error( message, ex );
-
-                if ( getCacheEventLogger() != null )
-                {
-                    getCacheEventLogger().logError( "RemoteCacheNoWaitFacade",
-                                                    ICacheEventLogger.UPDATE_EVENT,
-                                                    message + ":" + ex.getMessage() + " REGION: " + ce.getCacheName()
-                                                        + " ELEMENT: " + ce );
-                }
-
-                // can handle failover here? Is it safe to try the others?
-                // check to see it the noWait is now a zombie
-                // if it is a zombie, then move to the next in the failover list
-                // will need to keep them in order or a count
-                failover( nw );
-                // should start a failover thread
-                // should probably only failover if there is only one in the noWait
-                // list
-                // Should start a background thread to restore the original primary if we are in failover state.
-            }
-        }
-    }
-
-    /**
-     * Synchronously reads from the remote cache.
-     * <p>
-     * @param key
-     * @return Either an ICacheElement&lt;K, V&gt; or null if it is not found.
-     */
-    @Override
-    public ICacheElement<K, V> get( K key )
-    {
-        for (RemoteCacheNoWait<K, V> nw : noWaits)
-        {
-            try
-            {
-                ICacheElement<K, V> obj = nw.get( key );
-                if ( obj != null )
-                {
-                    return obj;
-                }
-            }
-            catch ( IOException ex )
-            {
-                log.debug( "Failed to get." );
-                return null;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Synchronously read from the remote cache.
-     * <p>
-     * @param pattern
-     * @return map
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMatching( String pattern )
-        throws IOException
-    {
-        for (RemoteCacheNoWait<K, V> nw : noWaits)
-        {
-            try
-            {
-                return nw.getMatching( pattern );
-            }
-            catch ( IOException ex )
-            {
-                log.debug( "Failed to getMatching." );
-            }
-        }
-        return Collections.emptyMap();
-    }
-
-    /**
-     * Gets multiple items from the cache based on the given set of keys.
-     * <p>
-     * @param keys
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache for any of these keys
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMultiple( Set<K> keys )
-    {
-        if ( keys != null && !keys.isEmpty() )
-        {
-            for (RemoteCacheNoWait<K, V> nw : noWaits)
-            {
-                try
-                {
-                    return nw.getMultiple( keys );
-                }
-                catch ( IOException ex )
-                {
-                    log.debug( "Failed to get." );
-                }
-            }
-        }
-
-        return Collections.emptyMap();
-    }
-
-    /**
-     * Return the keys in this cache.
-     * <p>
-     * @see org.apache.commons.jcs.auxiliary.AuxiliaryCache#getKeySet()
-     */
-    @Override
-    public Set<K> getKeySet() throws IOException
-    {
-        HashSet<K> allKeys = new HashSet<>();
-        for (RemoteCacheNoWait<K, V> nw : noWaits)
-        {
-            if ( nw != null )
-            {
-                Set<K> keys = nw.getKeySet();
-                if(keys != null)
-                {
-                    allKeys.addAll( keys );
-                }
-            }
-        }
-        return allKeys;
-    }
-
-    /**
-     * Adds a remove request to the remote cache.
-     * <p>
-     * @param key
-     * @return whether or not it was removed, right now it return false.
-     */
-    @Override
-    public boolean remove( K key )
-    {
-        try
-        {
-            for (RemoteCacheNoWait<K, V> nw : noWaits)
-            {
-                nw.remove( key );
-            }
-        }
-        catch ( IOException ex )
-        {
-            log.error( ex );
-        }
-        return false;
-    }
-
-    /**
-     * Adds a removeAll request to the remote cache.
-     */
-    @Override
-    public void removeAll()
-    {
-        try
-        {
-            for (RemoteCacheNoWait<K, V> nw : noWaits)
-            {
-                nw.removeAll();
-            }
-        }
-        catch ( IOException ex )
-        {
-            log.error( ex );
-        }
-    }
-
-    /** Adds a dispose request to the remote cache. */
-    @Override
-    public void dispose()
-    {
-        for (RemoteCacheNoWait<K, V> nw : noWaits)
-        {
-            nw.dispose();
-        }
-    }
-
-    /**
-     * No remote invocation.
-     * <p>
-     * @return The size value
-     */
-    @Override
-    public int getSize()
-    {
-        return 0;
-        // cache.getSize();
-    }
-
-    /**
-     * Gets the cacheType attribute of the RemoteCacheNoWaitFacade object.
-     * <p>
-     * @return The cacheType value
-     */
-    @Override
-    public CacheType getCacheType()
-    {
-        return CacheType.REMOTE_CACHE;
-    }
-
-    /**
-     * Gets the cacheName attribute of the RemoteCacheNoWaitFacade object.
-     * <p>
-     * @return The cacheName value
-     */
-    @Override
-    public String getCacheName()
-    {
-        return remoteCacheAttributes.getCacheName();
-    }
-
-    /**
-     * Gets the status attribute of the RemoteCacheNoWaitFacade object
-     * <p>
-     * Return ALIVE if any are alive.
-     * <p>
-     * @return The status value
-     */
-    @Override
-    public CacheStatus getStatus()
-    {
-        for (RemoteCacheNoWait<K, V> nw : noWaits)
-        {
-            if ( nw.getStatus() == CacheStatus.ALIVE )
-            {
-                return CacheStatus.ALIVE;
-            }
-        }
-
-        return CacheStatus.DISPOSED;
-    }
-
-    /**
-     * String form of some of the configuration information for the remote cache.
-     * <p>
-     * @return Some info for logging.
-     */
-    @Override
-    public String toString()
-    {
-        return "RemoteCacheNoWaitFacade: " + remoteCacheAttributes.getCacheName() + ", rca = " + remoteCacheAttributes;
-    }
-
-    /**
-     * Begin the failover process if this is a local cache. Clustered remote caches do not failover.
-     * <p>
-     * @param rcnw The no wait in error.
-     */
-    protected abstract void failover( RemoteCacheNoWait<K, V> rcnw );
-
-    /**
-     * Get the primary server from the list of failovers
-     *
-     * @return a no wait
-     */
-    public RemoteCacheNoWait<K, V> getPrimaryServer()
-    {
-        return noWaits.get(0);
-    }
-
-    /**
-     * restore the primary server in the list of failovers
-     *
-     */
-    public void restorePrimaryServer(RemoteCacheNoWait<K, V> rcnw)
-    {
-        noWaits.clear();
-        noWaits.add(rcnw);
-    }
-
-    /**
-     * @return Returns the AuxiliaryCacheAttributes.
-     */
-    @Override
-    public IRemoteCacheAttributes getAuxiliaryCacheAttributes()
-    {
-        return this.remoteCacheAttributes;
-    }
-
-    /**
-     * getStats
-     * @return String
-     */
-    @Override
-    public String getStats()
-    {
-        return getStatistics().toString();
-    }
-
-    /**
-     * @return statistics about the cache region
-     */
-    @Override
-    public IStats getStatistics()
-    {
-        IStats stats = new Stats();
-        stats.setTypeName( "Remote Cache No Wait Facade" );
-
-        ArrayList<IStatElement<?>> elems = new ArrayList<>();
-
-        if ( noWaits != null )
-        {
-            elems.add(new StatElement<>( "Number of No Waits", Integer.valueOf(noWaits.size()) ) );
-
-            for ( RemoteCacheNoWait<K, V> rcnw : noWaits )
-            {
-                // get the stats from the super too
-                IStats sStats = rcnw.getStatistics();
-                elems.addAll(sStats.getStatElements());
-            }
-        }
-
-        stats.setStatElements( elems );
-
-        return stats;
-    }
-
-    /**
-     * This typically returns end point info .
-     * <p>
-     * @return the name
-     */
-    @Override
-    public String getEventLoggingExtraInfo()
-    {
-        return "Remote Cache No Wait Facade";
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/CommonRemoteCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/CommonRemoteCacheAttributes.java
deleted file mode 100644
index 7ef69f9..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/CommonRemoteCacheAttributes.java
+++ /dev/null
@@ -1,295 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.AbstractAuxiliaryCacheAttributes;
-import org.apache.commons.jcs.auxiliary.remote.behavior.ICommonRemoteCacheAttributes;
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheConstants;
-import org.apache.commons.jcs.auxiliary.remote.server.behavior.RemoteType;
-
-/**
- * Attributes common to remote cache client and server.
- */
-public class CommonRemoteCacheAttributes
-    extends AbstractAuxiliaryCacheAttributes
-    implements ICommonRemoteCacheAttributes
-{
-    /** Don't change */
-    private static final long serialVersionUID = -1555143736942374000L;
-
-    /** The service name */
-    private String remoteServiceName = IRemoteCacheConstants.REMOTE_CACHE_SERVICE_VAL;
-
-    /** server host and port */
-    private RemoteLocation location;
-
-    /** Cluster chain */
-    private String clusterServers = "";
-
-    /** THe type of remote cache, local or cluster */
-    private RemoteType remoteType = RemoteType.LOCAL;
-
-    /** Should we issue a local remove if we get a put from a remote server */
-    private boolean removeUponRemotePut = true;
-
-    /** Can we receive from or put to the remote. this probably shouldn't be used. Use receive. */
-    private boolean getOnly = false;
-
-    /** Should we put and get from the clusters. */
-    private boolean localClusterConsistency = false;
-
-    /** read and connect timeout */
-    private int rmiSocketFactoryTimeoutMillis = DEFAULT_RMI_SOCKET_FACTORY_TIMEOUT_MILLIS;
-
-    /** Default constructor for the RemoteCacheAttributes object */
-    public CommonRemoteCacheAttributes()
-    {
-        super();
-    }
-
-    /**
-     * Gets the remoteTypeName attribute of the RemoteCacheAttributes object.
-     * <p>
-     * @return The remoteTypeName value
-     */
-    @Override
-    public String getRemoteTypeName()
-    {
-        return remoteType != null ? remoteType.toString() : RemoteType.LOCAL.toString();
-    }
-
-    /**
-     * Sets the remoteTypeName attribute of the RemoteCacheAttributes object.
-     * <p>
-     * @param s The new remoteTypeName value
-     */
-    @Override
-    public void setRemoteTypeName( String s )
-    {
-        RemoteType rt = RemoteType.valueOf(s);
-        if (rt != null)
-        {
-            this.remoteType = rt;
-        }
-    }
-
-    /**
-     * Gets the remoteType attribute of the RemoteCacheAttributes object.
-     * <p>
-     * @return The remoteType value
-     */
-    @Override
-    public RemoteType getRemoteType()
-    {
-        return remoteType;
-    }
-
-    /**
-     * Sets the remoteType attribute of the RemoteCacheAttributes object.
-     * <p>
-     * @param p The new remoteType value
-     */
-    @Override
-    public void setRemoteType( RemoteType p )
-    {
-        this.remoteType = p;
-    }
-
-    /**
-     * Gets the remoteServiceName attribute of the RemoteCacheAttributes object.
-     * <p>
-     * @return The remoteServiceName value
-     */
-    @Override
-    public String getRemoteServiceName()
-    {
-        return this.remoteServiceName;
-    }
-
-    /**
-     * Sets the remoteServiceName attribute of the RemoteCacheAttributes object.
-     * <p>
-     * @param s The new remoteServiceName value
-     */
-    @Override
-    public void setRemoteServiceName( String s )
-    {
-        this.remoteServiceName = s;
-    }
-
-    /**
-     * Sets the location attribute of the RemoteCacheAttributes object.
-     * <p>
-     * @param location The new location value
-     */
-    @Override
-    public void setRemoteLocation( RemoteLocation location )
-    {
-        this.location = location;
-    }
-
-    /**
-     * Sets the location attribute of the RemoteCacheAttributes object.
-     * <p>
-     * @param host The new remoteHost value
-     * @param port The new remotePort value
-     */
-    @Override
-    public void setRemoteLocation( String host, int port )
-    {
-        this.location = new RemoteLocation(host, port);
-    }
-
-    /**
-     * Gets the location attribute of the RemoteCacheAttributes object.
-     * <p>
-     * @return The remote location value
-     */
-    @Override
-    public RemoteLocation getRemoteLocation()
-    {
-        return this.location;
-    }
-
-    /**
-     * Gets the clusterServers attribute of the RemoteCacheAttributes object.
-     * <p>
-     * @return The clusterServers value
-     */
-    @Override
-    public String getClusterServers()
-    {
-        return this.clusterServers;
-    }
-
-    /**
-     * Sets the clusterServers attribute of the RemoteCacheAttributes object.
-     * <p>
-     * @param s The new clusterServers value
-     */
-    @Override
-    public void setClusterServers( String s )
-    {
-        this.clusterServers = s;
-    }
-
-    /**
-     * Gets the removeUponRemotePut attribute of the RemoteCacheAttributes object.
-     * <p>
-     * @return The removeUponRemotePut value
-     */
-    @Override
-    public boolean getRemoveUponRemotePut()
-    {
-        return this.removeUponRemotePut;
-    }
-
-    /**
-     * Sets the removeUponRemotePut attribute of the RemoteCacheAttributes object.
-     * <p>
-     * @param r The new removeUponRemotePut value
-     */
-    @Override
-    public void setRemoveUponRemotePut( boolean r )
-    {
-        this.removeUponRemotePut = r;
-    }
-
-    /**
-     * Gets the getOnly attribute of the RemoteCacheAttributes object.
-     * <p>
-     * @return The getOnly value
-     */
-    @Override
-    public boolean getGetOnly()
-    {
-        return this.getOnly;
-    }
-
-    /**
-     * Sets the getOnly attribute of the RemoteCacheAttributes object
-     * @param r The new getOnly value
-     */
-    @Override
-    public void setGetOnly( boolean r )
-    {
-        this.getOnly = r;
-    }
-
-    /**
-     * Should cluster updates be propagated to the locals.
-     * <p>
-     * @return The localClusterConsistency value
-     */
-    @Override
-    public boolean isLocalClusterConsistency()
-    {
-        return localClusterConsistency;
-    }
-
-    /**
-     * Should cluster updates be propagated to the locals.
-     * <p>
-     * @param r The new localClusterConsistency value
-     */
-    @Override
-    public void setLocalClusterConsistency( boolean r )
-    {
-        this.localClusterConsistency = r;
-    }
-
-    /**
-     * @param rmiSocketFactoryTimeoutMillis The rmiSocketFactoryTimeoutMillis to set.
-     */
-    @Override
-    public void setRmiSocketFactoryTimeoutMillis( int rmiSocketFactoryTimeoutMillis )
-    {
-        this.rmiSocketFactoryTimeoutMillis = rmiSocketFactoryTimeoutMillis;
-    }
-
-    /**
-     * @return Returns the rmiSocketFactoryTimeoutMillis.
-     */
-    @Override
-    public int getRmiSocketFactoryTimeoutMillis()
-    {
-        return rmiSocketFactoryTimeoutMillis;
-    }
-
-    /**
-     * @return String, all the important values that can be configured
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder buf = new StringBuilder();
-        buf.append( "\n RemoteCacheAttributes " );
-        if (this.location != null)
-        {
-            buf.append( "\n remoteHost = [" + this.location.getHost() + "]" );
-            buf.append( "\n remotePort = [" + this.location.getPort() + "]" );
-        }
-        buf.append( "\n cacheName = [" + getCacheName() + "]" );
-        buf.append( "\n remoteType = [" + remoteType + "]" );
-        buf.append( "\n removeUponRemotePut = [" + this.removeUponRemotePut + "]" );
-        buf.append( "\n getOnly = [" + getOnly + "]" );
-        return buf.toString();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteCache.java
deleted file mode 100644
index de06f59..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteCache.java
+++ /dev/null
@@ -1,209 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.util.ArrayList;
-
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheAttributes;
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheListener;
-import org.apache.commons.jcs.auxiliary.remote.server.behavior.RemoteType;
-import org.apache.commons.jcs.engine.ZombieCacheServiceNonLocal;
-import org.apache.commons.jcs.engine.behavior.ICacheServiceNonLocal;
-import org.apache.commons.jcs.engine.stats.StatElement;
-import org.apache.commons.jcs.engine.stats.Stats;
-import org.apache.commons.jcs.engine.stats.behavior.IStatElement;
-import org.apache.commons.jcs.engine.stats.behavior.IStats;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * Client proxy for an RMI remote cache.
- * <p>
- * This handles gets, updates, and removes. It also initiates failover recovery when an error is
- * encountered.
- */
-public class RemoteCache<K, V>
-    extends AbstractRemoteAuxiliaryCache<K, V>
-{
-    /** The logger. */
-    private static final Log log = LogManager.getLog( RemoteCache.class );
-
-    /** for error notifications */
-    private RemoteCacheMonitor monitor;
-
-    /** back link for failover initiation */
-    private AbstractRemoteCacheNoWaitFacade<K, V> facade;
-
-    /**
-     * Constructor for the RemoteCache object. This object communicates with a remote cache server.
-     * One of these exists for each region. This also holds a reference to a listener. The same
-     * listener is used for all regions for one remote server. Holding a reference to the listener
-     * allows this object to know the listener id assigned by the remote cache.
-     * <p>
-     * @param cattr the cache configuration
-     * @param remote the remote cache server handle
-     * @param listener a listener
-     * @param monitor the cache monitor
-     */
-    public RemoteCache( IRemoteCacheAttributes cattr,
-        ICacheServiceNonLocal<K, V> remote,
-        IRemoteCacheListener<K, V> listener,
-        RemoteCacheMonitor monitor )
-    {
-        super( cattr, remote, listener );
-        this.monitor = monitor;
-
-        RemoteUtils.configureGlobalCustomSocketFactory( getRemoteCacheAttributes().getRmiSocketFactoryTimeoutMillis() );
-    }
-
-    /**
-     * @return IStats object
-     */
-    @Override
-    public IStats getStatistics()
-    {
-        IStats stats = new Stats();
-        stats.setTypeName( "Remote Cache" );
-
-        ArrayList<IStatElement<?>> elems = new ArrayList<>();
-
-        elems.add(new StatElement<>( "Remote Host:Port", getIPAddressForService() ) );
-        elems.add(new StatElement<>( "Remote Type", this.getRemoteCacheAttributes().getRemoteTypeName() ) );
-
-//      if ( this.getRemoteCacheAttributes().getRemoteType() == RemoteType.CLUSTER )
-//      {
-//          // something cluster specific
-//      }
-
-        // get the stats from the super too
-        IStats sStats = super.getStatistics();
-        elems.addAll(sStats.getStatElements());
-
-        stats.setStatElements( elems );
-
-        return stats;
-    }
-
-    /**
-     * Set facade
-     *
-     * @param facade the facade to set
-     */
-    protected void setFacade(AbstractRemoteCacheNoWaitFacade<K, V> facade)
-    {
-        this.facade = facade;
-    }
-
-    /**
-     * Get facade
-     *
-     * @return the facade
-     */
-    protected AbstractRemoteCacheNoWaitFacade<K, V> getFacade()
-    {
-        return facade;
-    }
-
-    /**
-     * Handles exception by disabling the remote cache service before re-throwing the exception in
-     * the form of an IOException.
-     * <p>
-     * @param ex
-     * @param msg
-     * @param eventName
-     * @throws IOException
-     */
-    @Override
-    protected void handleException( Exception ex, String msg, String eventName )
-        throws IOException
-    {
-        String message = "Disabling remote cache due to error: " + msg;
-
-        logError( cacheName, "", message );
-        log.error( message, ex );
-
-        // we should not switch if the existing is a zombie.
-        if ( getRemoteCacheService() == null || !( getRemoteCacheService() instanceof ZombieCacheServiceNonLocal ) )
-        {
-            // TODO make configurable
-            setRemoteCacheService( new ZombieCacheServiceNonLocal<>( getRemoteCacheAttributes().getZombieQueueMaxSize() ) );
-        }
-        // may want to flush if region specifies
-        // Notify the cache monitor about the error, and kick off the recovery
-        // process.
-        monitor.notifyError();
-
-        log.debug( "Initiating failover, rcnwf = {0}", facade );
-
-        if ( facade != null && facade.getAuxiliaryCacheAttributes().getRemoteType() == RemoteType.LOCAL )
-        {
-            log.debug( "Found facade, calling failover" );
-            // may need to remove the noWait index here. It will be 0 if it is
-            // local since there is only 1 possible listener.
-            facade.failover( facade.getPrimaryServer() );
-        }
-
-        if ( ex instanceof IOException )
-        {
-            throw (IOException) ex;
-        }
-        throw new IOException( ex );
-    }
-
-    /**
-     * Debugging info.
-     * <p>
-     * @return basic info about the RemoteCache
-     */
-    @Override
-    public String toString()
-    {
-        return "RemoteCache: " + cacheName + " attributes = " + getRemoteCacheAttributes();
-    }
-
-    /**
-     * Gets the extra info for the event log.
-     * <p>
-     * @return disk location
-     */
-    @Override
-    public String getEventLoggingExtraInfo()
-    {
-        return getIPAddressForService();
-    }
-
-    /**
-     * IP address for the service, if one is stored.
-     * <p>
-     * Protected for testing.
-     * <p>
-     * @return String
-     */
-    protected String getIPAddressForService()
-    {
-        String ipAddress = "(null)";
-        if (this.getRemoteCacheAttributes().getRemoteLocation() != null)
-        {
-            ipAddress = this.getRemoteCacheAttributes().getRemoteLocation().toString();
-        }
-        return ipAddress;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheAttributes.java
deleted file mode 100644
index 7d1fad4..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheAttributes.java
+++ /dev/null
@@ -1,263 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.List;
-
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheAttributes;
-
-/**
- * These objects are used to configure the remote cache client.
- */
-public class RemoteCacheAttributes
-    extends CommonRemoteCacheAttributes
-    implements IRemoteCacheAttributes
-{
-    /** Don't change */
-    private static final long serialVersionUID = -1555143736942374000L;
-
-    /**
-     * Failover servers will be used by local caches one at a time. Listeners will be registered
-     * with all cluster servers. If we add a get from cluster attribute we will have the ability to
-     * chain clusters and have them get from each other.
-     */
-    private String failoverServers = "";
-
-    /** callback */
-    private int localPort = 0;
-
-    /** what failover server we are connected to. */
-    private int failoverIndex = 0;
-
-    /** List of failover server addresses */
-    private List<RemoteLocation> failovers;
-
-    /** default name is remote_cache_client */
-    private String threadPoolName = "remote_cache_client";
-
-    /** must be greater than 0 for a pool to be used. */
-    private int getTimeoutMillis = -1;
-
-    /**
-     * Can we receive from the server. You might have a 0 local store and keep everything on the
-     * remote. If so, you don't want to be notified of updates.
-     */
-    private boolean receive = DEFAULT_RECEIVE;
-
-    /** If the primary fails, we will queue items before reconnect.  This limits the number of items that can be queued. */
-    private int zombieQueueMaxSize = DEFAULT_ZOMBIE_QUEUE_MAX_SIZE;
-
-    /** Default constructor for the RemoteCacheAttributes object */
-    public RemoteCacheAttributes()
-    {
-        super();
-    }
-
-    /**
-     * Gets the failoverIndex attribute of the RemoteCacheAttributes object.
-     * <p>
-     * @return The failoverIndex value
-     */
-    @Override
-    public int getFailoverIndex()
-    {
-        return failoverIndex;
-    }
-
-    /**
-     * Sets the failoverIndex attribute of the RemoteCacheAttributes object.
-     * <p>
-     * @param p The new failoverIndex value
-     */
-    @Override
-    public void setFailoverIndex( int p )
-    {
-        this.failoverIndex = p;
-    }
-
-    /**
-     * Gets the failovers attribute of the RemoteCacheAttributes object.
-     * <p>
-     * @return The failovers value
-     */
-    @Override
-    public List<RemoteLocation> getFailovers()
-    {
-        return this.failovers;
-    }
-
-    /**
-     * Sets the failovers attribute of the RemoteCacheAttributes object.
-     * <p>
-     * @param failovers The new failovers value
-     */
-    @Override
-    public void setFailovers( List<RemoteLocation> failovers )
-    {
-        this.failovers = failovers;
-    }
-
-    /**
-     * Gets the failoverServers attribute of the RemoteCacheAttributes object.
-     * <p>
-     * @return The failoverServers value
-     */
-    @Override
-    public String getFailoverServers()
-    {
-        return this.failoverServers;
-    }
-
-    /**
-     * Sets the failoverServers attribute of the RemoteCacheAttributes object.
-     * <p>
-     * @param s The new failoverServers value
-     */
-    @Override
-    public void setFailoverServers( String s )
-    {
-        this.failoverServers = s;
-    }
-
-    /**
-     * Gets the localPort attribute of the RemoteCacheAttributes object.
-     * <p>
-     * @return The localPort value
-     */
-    @Override
-    public int getLocalPort()
-    {
-        return this.localPort;
-    }
-
-    /**
-     * Sets the localPort attribute of the RemoteCacheAttributes object
-     * @param p The new localPort value
-     */
-    @Override
-    public void setLocalPort( int p )
-    {
-        this.localPort = p;
-    }
-
-    /**
-     * @return the name of the pool
-     */
-    @Override
-    public String getThreadPoolName()
-    {
-        return threadPoolName;
-    }
-
-    /**
-     * @param name
-     */
-    @Override
-    public void setThreadPoolName( String name )
-    {
-        threadPoolName = name;
-    }
-
-    /**
-     * @return getTimeoutMillis
-     */
-    @Override
-    public int getGetTimeoutMillis()
-    {
-        return getTimeoutMillis;
-    }
-
-    /**
-     * @param millis
-     */
-    @Override
-    public void setGetTimeoutMillis( int millis )
-    {
-        getTimeoutMillis = millis;
-    }
-
-    /**
-     * By default this option is true. If you set it to false, you will not receive updates or
-     * removes from the remote server.
-     * <p>
-     * @param receive
-     */
-    @Override
-    public void setReceive( boolean receive )
-    {
-        this.receive = receive;
-    }
-
-    /**
-     * If RECEIVE is false then the remote cache will not register a listener with the remote
-     * server. This allows you to configure a remote server as a repository from which you can get
-     * and to which you put, but from which you do not receive any notifications. That is, you will
-     * not receive updates or removes.
-     * <p>
-     * If you set this option to false, you should set your local memory size to 0.
-     * <p>
-     * The remote cache manager uses this value to decide whether or not to register a listener.
-     * @return the receive value.
-     */
-    @Override
-    public boolean isReceive()
-    {
-        return this.receive;
-    }
-
-    /**
-     * The number of elements the zombie queue will hold. This queue is used to store events if we
-     * loose our connection with the server.
-     * <p>
-     * @param zombieQueueMaxSize The zombieQueueMaxSize to set.
-     */
-    @Override
-    public void setZombieQueueMaxSize( int zombieQueueMaxSize )
-    {
-        this.zombieQueueMaxSize = zombieQueueMaxSize;
-    }
-
-    /**
-     * The number of elements the zombie queue will hold. This queue is used to store events if we
-     * loose our connection with the server.
-     * <p>
-     * @return Returns the zombieQueueMaxSize.
-     */
-    @Override
-    public int getZombieQueueMaxSize()
-    {
-        return zombieQueueMaxSize;
-    }
-
-    /**
-     * @return String, all the important values that can be configured
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder buf = new StringBuilder(super.toString());
-        buf.append( "\n receive = [" + isReceive() + "]" );
-        buf.append( "\n getTimeoutMillis = [" + getGetTimeoutMillis() + "]" );
-        buf.append( "\n threadPoolName = [" + getThreadPoolName() + "]" );
-        buf.append( "\n localClusterConsistency = [" + isLocalClusterConsistency() + "]" );
-        buf.append( "\n zombieQueueMaxSize = [" + getZombieQueueMaxSize() + "]" );
-        return buf.toString();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheFactory.java
deleted file mode 100644
index 25f18d6..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheFactory.java
+++ /dev/null
@@ -1,272 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.rmi.registry.Registry;
-import java.util.ArrayList;
-import java.util.StringTokenizer;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-import org.apache.commons.jcs.auxiliary.AbstractAuxiliaryCacheFactory;
-import org.apache.commons.jcs.auxiliary.AuxiliaryCache;
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes;
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheAttributes;
-import org.apache.commons.jcs.auxiliary.remote.server.behavior.RemoteType;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-
-/**
- * The RemoteCacheFactory creates remote caches for the cache hub. It returns a no wait facade which
- * is a wrapper around a no wait. The no wait object is either an active connection to a remote
- * cache or a balking zombie if the remote cache is not accessible. It should be transparent to the
- * clients.
- */
-public class RemoteCacheFactory
-    extends AbstractAuxiliaryCacheFactory
-{
-    /** Monitor thread */
-    private RemoteCacheMonitor monitor;
-
-    /** Contains mappings of RemoteLocation instance to RemoteCacheManager instance. */
-    private ConcurrentMap<RemoteLocation, RemoteCacheManager> managers;
-
-    /** Lock for initialization of manager instances */
-    private Lock managerLock;
-
-    /**
-     * For LOCAL clients we get a handle to all the failovers, but we do not register a listener
-     * with them. We create the RemoteCacheManager, but we do not get a cache.
-     * <p>
-     * The failover runner will get a cache from the manager. When the primary is restored it will
-     * tell the manager for the failover to deregister the listener.
-     * <p>
-     * @param iaca
-     * @param cacheMgr
-     * @param cacheEventLogger
-     * @param elementSerializer
-     * @return AuxiliaryCache
-     */
-    @Override
-    public <K, V> AuxiliaryCache<K, V> createCache(
-            AuxiliaryCacheAttributes iaca, ICompositeCacheManager cacheMgr,
-           ICacheEventLogger cacheEventLogger, IElementSerializer elementSerializer )
-    {
-        RemoteCacheAttributes rca = (RemoteCacheAttributes) iaca;
-
-        ArrayList<RemoteCacheNoWait<K,V>> noWaits = new ArrayList<>();
-
-        switch (rca.getRemoteType())
-        {
-            case LOCAL:
-                // a list to be turned into an array of failover server information
-                ArrayList<RemoteLocation> failovers = new ArrayList<>();
-
-                // not necessary if a failover list is defined
-                // REGISTER PRIMARY LISTENER
-                // if it is a primary
-                boolean primaryDefined = false;
-                if ( rca.getRemoteLocation() != null )
-                {
-                    primaryDefined = true;
-
-                    failovers.add( rca.getRemoteLocation() );
-                    RemoteCacheManager rcm = getManager( rca, cacheMgr, cacheEventLogger, elementSerializer );
-                    RemoteCacheNoWait<K,V> ic = rcm.getCache( rca );
-                    noWaits.add( ic );
-                }
-
-                // GET HANDLE BUT DONT REGISTER A LISTENER FOR FAILOVERS
-                String failoverList = rca.getFailoverServers();
-                if ( failoverList != null )
-                {
-                    StringTokenizer fit = new StringTokenizer( failoverList, "," );
-                    int fCnt = 0;
-                    while ( fit.hasMoreTokens() )
-                    {
-                        fCnt++;
-
-                        String server = fit.nextToken();
-                        RemoteLocation location = RemoteLocation.parseServerAndPort(server);
-
-                        if (location != null)
-                        {
-                            failovers.add( location );
-                            rca.setRemoteLocation(location);
-                            RemoteCacheManager rcm = getManager( rca, cacheMgr, cacheEventLogger, elementSerializer );
-
-                            // add a listener if there are none, need to tell rca what
-                            // number it is at
-                            if ( ( !primaryDefined && fCnt == 1 ) || noWaits.size() <= 0 )
-                            {
-                                RemoteCacheNoWait<K,V> ic = rcm.getCache( rca );
-                                noWaits.add( ic );
-                            }
-                        }
-                    }
-                    // end while
-                }
-                // end if failoverList != null
-
-                rca.setFailovers( failovers );
-                break;
-
-            case CLUSTER:
-                // REGISTER LISTENERS FOR EACH SYSTEM CLUSTERED CACHEs
-                StringTokenizer it = new StringTokenizer( rca.getClusterServers(), "," );
-                while ( it.hasMoreElements() )
-                {
-                    String server = (String) it.nextElement();
-                    RemoteLocation location = RemoteLocation.parseServerAndPort(server);
-
-                    if (location != null)
-                    {
-                        rca.setRemoteLocation(location);
-                        RemoteCacheManager rcm = getManager( rca, cacheMgr, cacheEventLogger, elementSerializer );
-                        rca.setRemoteType( RemoteType.CLUSTER );
-                        RemoteCacheNoWait<K,V> ic = rcm.getCache( rca );
-                        noWaits.add( ic );
-                    }
-                }
-                break;
-        }
-
-        RemoteCacheNoWaitFacade<K, V> rcnwf =
-            new RemoteCacheNoWaitFacade<>(noWaits, rca, cacheEventLogger, elementSerializer, this );
-
-        return rcnwf;
-    }
-
-    // end createCache
-
-    /**
-     * Returns an instance of RemoteCacheManager for the given connection parameters.
-     * <p>
-     * Host and Port uniquely identify a manager instance.
-     * <p>
-     * @param cattr
-     *
-     * @return The instance value or null if no such manager exists
-     */
-    public RemoteCacheManager getManager( IRemoteCacheAttributes cattr )
-    {
-        if ( cattr.getRemoteLocation() == null )
-        {
-            cattr.setRemoteLocation("", Registry.REGISTRY_PORT);
-        }
-
-        RemoteLocation loc = cattr.getRemoteLocation();
-        RemoteCacheManager ins = managers.get( loc );
-
-        return ins;
-    }
-
-    /**
-     * Returns an instance of RemoteCacheManager for the given connection parameters.
-     * <p>
-     * Host and Port uniquely identify a manager instance.
-     * <p>
-     * If the connection cannot be established, zombie objects will be used for future recovery
-     * purposes.
-     * <p>
-     * @param cattr
-     * @param cacheMgr
-     * @param cacheEventLogger
-     * @param elementSerializer
-     * @return The instance value, never null
-     */
-    public RemoteCacheManager getManager( IRemoteCacheAttributes cattr, ICompositeCacheManager cacheMgr,
-                                                  ICacheEventLogger cacheEventLogger,
-                                                  IElementSerializer elementSerializer )
-    {
-        RemoteCacheManager ins = getManager( cattr );
-
-        if ( ins == null )
-        {
-            managerLock.lock();
-
-            try
-            {
-                ins = managers.get( cattr.getRemoteLocation() );
-
-                if (ins == null)
-                {
-                    ins = new RemoteCacheManager( cattr, cacheMgr, monitor, cacheEventLogger, elementSerializer);
-                    managers.put( cattr.getRemoteLocation(), ins );
-                    monitor.addManager(ins);
-                }
-            }
-            finally
-            {
-                managerLock.unlock();
-            }
-        }
-
-        return ins;
-    }
-
-	/**
-	 * @see org.apache.commons.jcs.auxiliary.AbstractAuxiliaryCacheFactory#initialize()
-	 */
-	@Override
-	public void initialize()
-	{
-		super.initialize();
-
-		managers = new ConcurrentHashMap<>();
-		managerLock = new ReentrantLock();
-
-        monitor = new RemoteCacheMonitor();
-        monitor.setDaemon(true);
-	}
-
-	/**
-	 * @see org.apache.commons.jcs.auxiliary.AbstractAuxiliaryCacheFactory#dispose()
-	 */
-	@Override
-	public void dispose()
-	{
-		for (RemoteCacheManager manager : managers.values())
-		{
-			manager.release();
-		}
-
-		managers.clear();
-
-        if (monitor != null)
-        {
-            monitor.notifyShutdown();
-            try
-            {
-                monitor.join(5000);
-            }
-            catch (InterruptedException e)
-            {
-                // swallow
-            }
-            monitor = null;
-        }
-
-		super.dispose();
-	}
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheFailoverRunner.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheFailoverRunner.java
deleted file mode 100644
index effc4fd..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheFailoverRunner.java
+++ /dev/null
@@ -1,389 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.util.List;
-import java.util.ListIterator;
-
-import org.apache.commons.jcs.auxiliary.AbstractAuxiliaryCacheMonitor;
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheAttributes;
-import org.apache.commons.jcs.engine.CacheStatus;
-import org.apache.commons.jcs.engine.behavior.ICache;
-
-/**
- * The RemoteCacheFailoverRunner tries to establish a connection with a failover
- * server, if any are defined. Once a failover connection is made, it will
- * attempt to replace the failover with the primary remote server.
- * <p>
- * It works by switching out the RemoteCacheNoWait inside the Facade.
- * <p>
- * Client (i.e.) the CompositeCache has reference to a RemoteCacheNoWaitFacade.
- * This facade is created by the RemoteCacheFactory. The factory maintains a set
- * of managers, one for each remote server. Typically, there will only be one
- * manager.
- * <p>
- * If you use multiple remote servers, you may want to set one or more as
- * failovers. If a local cache cannot connect to the primary server, or looses
- * its connection to the primary server, it will attempt to restore that
- * Connection in the background. If failovers are defined, the Failover runner
- * will try to connect to a failover until the primary is restored.
- *
- */
-public class RemoteCacheFailoverRunner<K, V> extends AbstractAuxiliaryCacheMonitor
-{
-    /** The facade returned to the composite cache. */
-    private final RemoteCacheNoWaitFacade<K, V> facade;
-
-    /** Factory instance */
-    private final RemoteCacheFactory cacheFactory;
-
-    /**
-     * Constructor for the RemoteCacheFailoverRunner object. This allows the
-     * FailoverRunner to modify the facade that the CompositeCache references.
-     *
-     * @param facade the facade the CompositeCache talks to.
-     * @param cacheFactory the cache factory instance
-     */
-    public RemoteCacheFailoverRunner( RemoteCacheNoWaitFacade<K, V> facade, RemoteCacheFactory cacheFactory )
-    {
-        super("JCS-RemoteCacheFailoverRunner");
-        this.facade = facade;
-        this.cacheFactory = cacheFactory;
-        setIdlePeriod(20000L);
-    }
-
-    /**
-     * Clean up all resources before shutdown
-     */
-    @Override
-    protected void dispose()
-    {
-        // empty
-    }
-
-    /**
-     * do actual work
-     */
-    @Override
-    protected void doWork()
-    {
-        // empty
-    }
-
-
-    /**
-     * Main processing method for the RemoteCacheFailoverRunner object.
-     * <p>
-     * If we do not have a connection with any failover server, this will try to
-     * connect one at a time. If no connection can be made, it goes to sleep for
-     * a while (20 seconds).
-     * <p>
-     * Once a connection with a failover is made, we will try to reconnect to
-     * the primary server.
-     * <p>
-     * The primary server is the first server defines in the FailoverServers
-     * list.
-     */
-    @Override
-    public void run()
-    {
-        // start the main work of connecting to a failover and then restoring
-        // the primary.
-        connectAndRestore();
-
-        if ( log.isInfoEnabled() )
-        {
-            int failoverIndex = facade.getAuxiliaryCacheAttributes().getFailoverIndex();
-            log.info( "Exiting failover runner. Failover index = {0}", failoverIndex);
-
-            if ( failoverIndex <= 0 )
-            {
-                log.info( "Failover index is <= 0, meaning we are not connected to a failover server." );
-            }
-            else if ( failoverIndex > 0 )
-            {
-                log.info( "Failover index is > 0, meaning we are connected to a failover server." );
-            }
-            // log if we are allright or not.
-        }
-    }
-
-    /**
-     * This is the main loop. If there are failovers defined, then this will
-     * continue until the primary is re-connected. If no failovers are defined,
-     * this will exit automatically.
-     */
-    private void connectAndRestore()
-    {
-        IRemoteCacheAttributes rca0 = facade.getAuxiliaryCacheAttributes();
-
-        do
-        {
-            log.info( "Remote cache FAILOVER RUNNING." );
-
-            // there is no active listener
-            if ( !allright.get() )
-            {
-                // Monitor each RemoteCacheManager instance one after the other.
-                // Each RemoteCacheManager corresponds to one remote connection.
-                List<RemoteLocation> failovers = rca0.getFailovers();
-                // we should probably check to see if there are any failovers,
-                // even though the caller
-                // should have already.
-
-                if ( failovers == null )
-                {
-                    log.warn( "Remote is misconfigured, failovers was null." );
-                    return;
-                }
-                else if ( failovers.size() == 1 )
-                {
-                    // if there is only the primary, return out of this
-                    log.info( "No failovers defined, exiting failover runner." );
-                    return;
-                }
-
-                int fidx = rca0.getFailoverIndex();
-                log.debug( "fidx = {0} failovers.size = {1}", () -> fidx,
-                        () -> failovers.size() );
-
-                // shouldn't we see if the primary is backup?
-                // If we don't check the primary, if it gets connected in the
-                // background,
-                // we will disconnect it only to put it right back
-                ListIterator<RemoteLocation> i = failovers.listIterator(fidx); // + 1; // +1 skips the primary
-                log.debug( "starting at failover i = {0}", i );
-
-                // try them one at a time until successful
-                for ( ; i.hasNext() && !allright.get();)
-                {
-                    RemoteLocation server = i.next();
-                    log.debug( "Trying server [{1}] at failover index i = {1}",
-                            server, i );
-
-                    RemoteCacheAttributes rca = (RemoteCacheAttributes) rca0.clone();
-                    rca.setRemoteLocation(server);
-                    RemoteCacheManager rcm = cacheFactory.getManager( rca );
-
-                    log.debug( "RemoteCacheAttributes for failover = {0}", rca );
-
-                    if (rcm != null)
-                    {
-                        // add a listener if there are none, need to tell rca
-                        // what number it is at
-                        ICache<K, V> ic = rcm.getCache( rca );
-                        if ( ic.getStatus() == CacheStatus.ALIVE )
-                        {
-                            // may need to do this more gracefully
-                            log.debug( "resetting no wait" );
-                            facade.restorePrimaryServer((RemoteCacheNoWait<K, V>) ic);
-                            rca0.setFailoverIndex( i.nextIndex() );
-
-                            log.debug( "setting ALLRIGHT to true" );
-                            if ( i.hasPrevious() )
-                            {
-                                log.debug( "Moving to Primary Recovery Mode, failover index = {0}", i );
-                            }
-                            else
-                            {
-                                log.debug( "No need to connect to failover, the primary server is back up." );
-                            }
-
-                            allright.set(true);
-
-                            log.info( "CONNECTED to host = [{0}]",
-                                    () -> rca.getRemoteLocation() );
-                        }
-                    }
-                }
-            }
-            // end if !allright
-            // get here if while index >0 and allright, meaning that we are
-            // connected to some backup server.
-            else
-            {
-                log.debug( "ALLRIGHT is true " );
-                log.info( "Failover runner is in primary recovery mode. "
-                        + "Failover index = {0} Will now try to reconnect to "
-                        + "primary server.", () -> rca0.getFailoverIndex() );
-            }
-
-            boolean primaryRestoredSuccessfully = false;
-            // if we are not connected to the primary, try.
-            if ( rca0.getFailoverIndex() > 0 )
-            {
-                primaryRestoredSuccessfully = restorePrimary();
-                log.debug( "Primary recovery success state = {0}",
-                        primaryRestoredSuccessfully );
-            }
-
-            if ( !primaryRestoredSuccessfully )
-            {
-                // Time driven mode: sleep between each round of recovery
-                // attempt.
-                try
-                {
-                    log.warn( "Failed to reconnect to primary server. "
-                            + "Cache failover runner is going to sleep for "
-                            + "{0} milliseconds.", idlePeriod );
-                    Thread.sleep( idlePeriod );
-                }
-                catch ( InterruptedException ex )
-                {
-                    // ignore;
-                }
-            }
-
-            // try to bring the listener back to the primary
-        }
-        while ( rca0.getFailoverIndex() > 0 || !allright.get() );
-        // continue if the primary is not restored or if things are not allright.
-    }
-
-    /**
-     * Try to restore the primary server.
-     * <p>
-     * Once primary is restored the failover listener must be deregistered.
-     * <p>
-     * The primary server is the first server defines in the FailoverServers
-     * list.
-     *
-     * @return boolean value indicating whether the restoration was successful
-     */
-    private boolean restorePrimary()
-    {
-        IRemoteCacheAttributes rca0 = facade.getAuxiliaryCacheAttributes();
-        // try to move back to the primary
-        RemoteLocation server = rca0.getFailovers().get(0);
-
-        log.info( "Trying to restore connection to primary remote server "
-                + "[{0}]", server );
-
-        RemoteCacheAttributes rca = (RemoteCacheAttributes) rca0.clone();
-        rca.setRemoteLocation(server);
-        RemoteCacheManager rcm = cacheFactory.getManager( rca );
-
-        if (rcm != null)
-        {
-            // add a listener if there are none, need to tell rca what number it
-            // is at
-            ICache<K, V> ic = rcm.getCache( rca );
-            // by default the listener id should be 0, else it will be the
-            // listener
-            // Originally associated with the remote cache. either way is fine.
-            // We just don't want the listener id from a failover being used.
-            // If the remote server was rebooted this could be a problem if new
-            // locals were also added.
-
-            if ( ic.getStatus() == CacheStatus.ALIVE )
-            {
-                try
-                {
-                    // we could have more than one listener registered right
-                    // now.
-                    // this will not result in a loop, only duplication
-                    // stop duplicate listening.
-                    if ( facade.getPrimaryServer() != null && facade.getPrimaryServer().getStatus() == CacheStatus.ALIVE )
-                    {
-                        int fidx = rca0.getFailoverIndex();
-
-                        if ( fidx > 0 )
-                        {
-                            RemoteLocation serverOld = rca0.getFailovers().get(fidx);
-
-                            log.debug( "Failover Index = {0} the server at that "
-                                    + "index is [{1}]", fidx, serverOld );
-
-                            if ( serverOld != null )
-                            {
-                                // create attributes that reflect the
-                                // previous failed over configuration.
-                                RemoteCacheAttributes rcaOld = (RemoteCacheAttributes) rca0.clone();
-                                rcaOld.setRemoteLocation(serverOld);
-                                RemoteCacheManager rcmOld = cacheFactory.getManager( rcaOld );
-
-                                if ( rcmOld != null )
-                                {
-                                    // manager can remove by name if
-                                    // necessary
-                                    rcmOld.removeRemoteCacheListener( rcaOld );
-                                }
-                                log.info( "Successfully deregistered from "
-                                        + "FAILOVER remote server = {0}", serverOld );
-                            }
-                        }
-                        else if ( fidx == 0 )
-                        {
-                            // this should never happen. If there are no
-                            // failovers this shouldn't get called.
-                            if ( log.isDebugEnabled() )
-                            {
-                                log.debug( "No need to restore primary, it is already restored." );
-                                return true;
-                            }
-                        }
-                        else if ( fidx < 0 )
-                        {
-                            // this should never happen
-                            log.warn( "Failover index is less than 0, this shouldn't happen" );
-                        }
-                    }
-                }
-                catch ( IOException e )
-                {
-                    // TODO, should try again, or somehow stop the listener
-                    log.error("Trouble trying to deregister old failover "
-                            + "listener prior to restoring the primary = {0}",
-                            server, e );
-                }
-
-                // Restore primary
-                // may need to do this more gracefully, letting the failover finish in the background
-                RemoteCacheNoWait<K, V> failoverNoWait = facade.getPrimaryServer();
-
-                // swap in a new one
-                facade.restorePrimaryServer((RemoteCacheNoWait<K, V>) ic);
-                rca0.setFailoverIndex( 0 );
-
-                String message = "Successfully reconnected to PRIMARY "
-                        + "remote server. Substituted primary for "
-                        + "failoverNoWait [" + failoverNoWait + "]";
-                log.info( message );
-
-                if ( facade.getCacheEventLogger() != null )
-                {
-                    facade.getCacheEventLogger().logApplicationEvent(
-                            "RemoteCacheFailoverRunner", "RestoredPrimary",
-                            message );
-                }
-                return true;
-            }
-        }
-
-        // else all right
-        // if the failover index was at 0 here, we would be in a bad
-        // situation, unless there were just
-        // no failovers configured.
-        log.debug( "Primary server status in error, not connected." );
-
-        return false;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheListener.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheListener.java
deleted file mode 100644
index 29942fe..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheListener.java
+++ /dev/null
@@ -1,115 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.rmi.RemoteException;
-import java.rmi.server.UnicastRemoteObject;
-
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheAttributes;
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheConstants;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * Registered with RemoteCache server. The server updates the local caches via this listener. Each
- * server assigns a unique listener id for a listener.
- * <p>
- * One listener is used per remote cache server. The same listener is used for all the regions that
- * talk to a particular server.
- */
-public class RemoteCacheListener<K, V>
-    extends AbstractRemoteCacheListener<K, V>
-    implements IRemoteCacheConstants
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog( RemoteCacheListener.class );
-
-    /** Has this client been shutdown. */
-    private boolean disposed = false;
-
-    /**
-     * Only need one since it does work for all regions, just reference by multiple region names.
-     * <p>
-     * The constructor exports this object, making it available to receive incoming calls. The
-     * callback port is anonymous unless a local port value was specified in the configuration.
-     * <p>
-     * @param irca cache configuration
-     * @param cacheMgr the cache hub
-     * @param elementSerializer a custom serializer
-     */
-    public RemoteCacheListener( IRemoteCacheAttributes irca, ICompositeCacheManager cacheMgr, IElementSerializer elementSerializer )
-    {
-        super( irca, cacheMgr, elementSerializer );
-
-        // Export this remote object to make it available to receive incoming
-        // calls.
-        try
-        {
-            UnicastRemoteObject.exportObject( this, irca.getLocalPort() );
-        }
-        catch ( RemoteException ex )
-        {
-            log.error( "Problem exporting object.", ex );
-            throw new IllegalStateException( ex.getMessage() );
-        }
-    }
-
-    /**
-     * Deregister itself.
-     * <p>
-     * @throws IOException
-     */
-    @Override
-    public synchronized void dispose()
-        throws IOException
-    {
-        if ( !disposed )
-        {
-            log.info( "Unexporting listener." );
-            try
-            {
-                UnicastRemoteObject.unexportObject( this, true );
-            }
-            catch ( RemoteException ex )
-            {
-                log.error( "Problem unexporting the listener.", ex );
-                throw new IllegalStateException( ex.getMessage() );
-            }
-            disposed = true;
-        }
-    }
-
-    /**
-     * For easier debugging.
-     * <p>
-     * @return Basic info on this listener.
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder buf = new StringBuilder();
-        buf.append( "\n RemoteCacheListener: " );
-        buf.append( super.toString() );
-        return buf.toString();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheManager.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheManager.java
deleted file mode 100644
index ac40eef..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheManager.java
+++ /dev/null
@@ -1,348 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.rmi.Naming;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheAttributes;
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheClient;
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheListener;
-import org.apache.commons.jcs.engine.CacheStatus;
-import org.apache.commons.jcs.engine.CacheWatchRepairable;
-import org.apache.commons.jcs.engine.ZombieCacheServiceNonLocal;
-import org.apache.commons.jcs.engine.ZombieCacheWatch;
-import org.apache.commons.jcs.engine.behavior.ICacheObserver;
-import org.apache.commons.jcs.engine.behavior.ICacheServiceNonLocal;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * An instance of RemoteCacheManager corresponds to one remote connection of a specific host and
- * port. All RemoteCacheManager instances are monitored by the singleton RemoteCacheMonitor
- * monitoring daemon for error detection and recovery.
- * <p>
- * Getting an instance of the remote cache has the effect of getting a handle on the remote server.
- * Listeners are not registered with the server until a cache is requested from the manager.
- */
-public class RemoteCacheManager
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog( RemoteCacheManager.class );
-
-    /** Contains instances of RemoteCacheNoWait managed by a RemoteCacheManager instance. */
-    private final ConcurrentMap<String, RemoteCacheNoWait<?, ?>> caches =
-            new ConcurrentHashMap<>();
-
-    /** The event logger. */
-    private final ICacheEventLogger cacheEventLogger;
-
-    /** The serializer. */
-    private final IElementSerializer elementSerializer;
-
-    /** Handle to the remote cache service; or a zombie handle if failed to connect. */
-    private ICacheServiceNonLocal<?, ?> remoteService;
-
-    /**
-     * Wrapper of the remote cache watch service; or wrapper of a zombie service if failed to
-     * connect.
-     */
-    private CacheWatchRepairable remoteWatch;
-
-    /** The cache manager listeners will need to use to get a cache. */
-    private final ICompositeCacheManager cacheMgr;
-
-    /** For error notification */
-    private final RemoteCacheMonitor monitor;
-
-    /** The service found through lookup */
-    private final String registry;
-
-    /** can it be restored */
-    private boolean canFix = true;
-
-    /**
-     * Constructs an instance to with the given remote connection parameters. If the connection
-     * cannot be made, "zombie" services will be temporarily used until a successful re-connection
-     * is made by the monitoring daemon.
-     * <p>
-     * @param cattr cache attributes
-     * @param cacheMgr the cache hub
-     * @param monitor the cache monitor thread for error notifications
-     * @param cacheEventLogger
-     * @param elementSerializer
-     */
-    protected RemoteCacheManager( IRemoteCacheAttributes cattr, ICompositeCacheManager cacheMgr,
-                                RemoteCacheMonitor monitor,
-                                ICacheEventLogger cacheEventLogger, IElementSerializer elementSerializer)
-    {
-        this.cacheMgr = cacheMgr;
-        this.monitor = monitor;
-        this.cacheEventLogger = cacheEventLogger;
-        this.elementSerializer = elementSerializer;
-        this.remoteWatch = new CacheWatchRepairable();
-
-        this.registry = RemoteUtils.getNamingURL(cattr.getRemoteLocation(), cattr.getRemoteServiceName());
-
-        try
-        {
-            lookupRemoteService();
-        }
-        catch (IOException e)
-        {
-            log.error("Could not find server", e);
-            // Notify the cache monitor about the error, and kick off the
-            // recovery process.
-            monitor.notifyError();
-        }
-    }
-
-    /**
-     * Lookup remote service from registry
-     * @throws IOException if the remote service could not be found
-     *
-     */
-    protected void lookupRemoteService() throws IOException
-    {
-        log.info( "Looking up server [{0}]", registry );
-        try
-        {
-            Object obj = Naming.lookup( registry );
-            log.info( "Server found: {0}", obj );
-
-            // Successful connection to the remote server.
-            this.remoteService = (ICacheServiceNonLocal<?, ?>) obj;
-            log.debug( "Remote Service = {0}", remoteService );
-            remoteWatch.setCacheWatch( (ICacheObserver) remoteService );
-        }
-        catch ( Exception ex )
-        {
-            // Failed to connect to the remote server.
-            // Configure this RemoteCacheManager instance to use the "zombie"
-            // services.
-            this.remoteService = new ZombieCacheServiceNonLocal<>();
-            remoteWatch.setCacheWatch( new ZombieCacheWatch() );
-            throw new IOException( "Problem finding server at [" + registry + "]", ex );
-        }
-    }
-
-    /**
-     * Adds the remote cache listener to the underlying cache-watch service.
-     * <p>
-     * @param cattr The feature to be added to the RemoteCacheListener attribute
-     * @param listener The feature to be added to the RemoteCacheListener attribute
-     * @throws IOException
-     */
-    public <K, V> void addRemoteCacheListener( IRemoteCacheAttributes cattr, IRemoteCacheListener<K, V> listener )
-        throws IOException
-    {
-        if ( cattr.isReceive() )
-        {
-            log.info( "The remote cache is configured to receive events from the remote server. "
-                + "We will register a listener. remoteWatch = {0} | IRemoteCacheListener = {1}"
-                + " | cacheName ", remoteWatch, listener, cattr.getCacheName() );
-
-            remoteWatch.addCacheListener( cattr.getCacheName(), listener );
-        }
-        else
-        {
-            log.info( "The remote cache is configured to NOT receive events from the remote server. "
-                    + "We will NOT register a listener." );
-        }
-    }
-
-    /**
-     * Removes a listener. When the primary recovers the failover must deregister itself for a
-     * region. The failover runner will call this method to de-register. We do not want to deregister
-     * all listeners to a remote server, in case a failover is a primary of another region. Having
-     * one regions failover act as another servers primary is not currently supported.
-     * <p>
-     * @param cattr
-     * @throws IOException
-     */
-    public void removeRemoteCacheListener( IRemoteCacheAttributes cattr )
-        throws IOException
-    {
-        RemoteCacheNoWait<?, ?> cache = caches.get( cattr.getCacheName() );
-        if ( cache != null )
-        {
-        	removeListenerFromCache(cache);
-        }
-        else
-        {
-            if ( cattr.isReceive() )
-            {
-                log.warn( "Trying to deregister Cache Listener that was never registered." );
-            }
-            else
-            {
-                log.debug( "Since the remote cache is configured to not receive, "
-                    + "there is no listener to deregister." );
-            }
-        }
-    }
-
-    // common helper method
-	private void removeListenerFromCache(RemoteCacheNoWait<?, ?> cache) throws IOException
-	{
-		IRemoteCacheClient<?, ?> rc = cache.getRemoteCache();
-	    log.debug( "Found cache for [{0}], deregistering listener.",
-	            () -> cache.getCacheName() );
-		// could also store the listener for a server in the manager.
-		IRemoteCacheListener<?, ?> listener = rc.getListener();
-        remoteWatch.removeCacheListener( cache.getCacheName(), listener );
-	}
-
-    /**
-     * Gets a RemoteCacheNoWait from the RemoteCacheManager. The RemoteCacheNoWait objects are
-     * identified by the cache name value of the RemoteCacheAttributes object.
-     * <p>
-     * If the client is configured to register a listener, this call results on a listener being
-     * created if one isn't already registered with the remote cache for this region.
-     * <p>
-     * @param cattr
-     * @return The cache value
-     */
-    @SuppressWarnings("unchecked") // Need to cast because of common map for all caches
-    public <K, V> RemoteCacheNoWait<K, V> getCache( IRemoteCacheAttributes cattr )
-    {
-        RemoteCacheNoWait<K, V> remoteCacheNoWait =
-                (RemoteCacheNoWait<K, V>) caches.computeIfAbsent(cattr.getCacheName(), key -> {
-                    return newRemoteCacheNoWait(cattr);
-                });
-
-        // might want to do some listener sanity checking here.
-        return remoteCacheNoWait;
-    }
-
-    /**
-     * Create new RemoteCacheNoWait instance
-     *
-     * @param cattr the cache configuration
-     * @return the instance
-     */
-    protected <K, V> RemoteCacheNoWait<K, V> newRemoteCacheNoWait(IRemoteCacheAttributes cattr)
-    {
-        RemoteCacheNoWait<K, V> remoteCacheNoWait;
-        // create a listener first and pass it to the remotecache
-        // sender.
-        RemoteCacheListener<K, V> listener = null;
-        try
-        {
-            listener = new RemoteCacheListener<>( cattr, cacheMgr, elementSerializer );
-            addRemoteCacheListener( cattr, listener );
-        }
-        catch ( Exception e )
-        {
-            log.error( "Problem adding listener. RemoteCacheListener = {0}",
-                    listener, e );
-        }
-
-        IRemoteCacheClient<K, V> remoteCacheClient =
-            new RemoteCache<>( cattr, (ICacheServiceNonLocal<K, V>) remoteService, listener, monitor );
-        remoteCacheClient.setCacheEventLogger( cacheEventLogger );
-        remoteCacheClient.setElementSerializer( elementSerializer );
-
-        remoteCacheNoWait = new RemoteCacheNoWait<>( remoteCacheClient );
-        remoteCacheNoWait.setCacheEventLogger( cacheEventLogger );
-        remoteCacheNoWait.setElementSerializer( elementSerializer );
-
-        return remoteCacheNoWait;
-    }
-
-    /** Shutdown all. */
-    public void release()
-    {
-        for (RemoteCacheNoWait<?, ?> c : caches.values())
-        {
-            try
-            {
-                log.info( "freeCache [{0}]", () -> c.getCacheName() );
-
-                removeListenerFromCache(c);
-                c.dispose();
-            }
-            catch ( IOException ex )
-            {
-                log.error( "Problem releasing {0}", c.getCacheName(), ex );
-            }
-        }
-
-        caches.clear();
-    }
-
-    /**
-     * Fixes up all the caches managed by this cache manager.
-     */
-    public void fixCaches()
-    {
-        if ( !canFix )
-        {
-            return;
-        }
-
-        log.info( "Fixing caches. ICacheServiceNonLocal {0} | IRemoteCacheObserver {1}",
-                remoteService, remoteWatch );
-
-        for (RemoteCacheNoWait<?, ?> c : caches.values())
-        {
-            if (c.getStatus() == CacheStatus.ERROR)
-            {
-                c.fixCache( remoteService );
-            }
-        }
-
-        if ( log.isInfoEnabled() )
-        {
-            String msg = "Remote connection to " + registry + " resumed.";
-            if ( cacheEventLogger != null )
-            {
-                cacheEventLogger.logApplicationEvent( "RemoteCacheManager", "fix", msg );
-            }
-            log.info( msg );
-        }
-    }
-
-    /**
-     * Returns true if the connection to the remote host can be
-     * successfully re-established.
-     * <p>
-     * @return true if we found a failover server
-     */
-    public boolean canFixCaches()
-    {
-        try
-        {
-            lookupRemoteService();
-        }
-        catch (IOException e)
-        {
-            log.error("Could not find server", e);
-            canFix = false;
-        }
-
-        return canFix;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheMonitor.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheMonitor.java
deleted file mode 100644
index d33ad53..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheMonitor.java
+++ /dev/null
@@ -1,100 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.apache.commons.jcs.auxiliary.AbstractAuxiliaryCacheMonitor;
-
-/**
- * Used to monitor and repair any failed connection for the remote cache service. By default the
- * monitor operates in a failure driven mode. That is, it goes into a wait state until there is an
- * error.
- *
- * TODO consider moving this into an active monitoring mode. Upon the notification of a
- * connection error, the monitor changes to operate in a time driven mode. That is, it attempts to
- * recover the connections on a periodic basis. When all failed connections are restored, it changes
- * back to the failure driven mode.
- */
-public class RemoteCacheMonitor extends AbstractAuxiliaryCacheMonitor
-{
-    /**
-     * Map of managers to monitor
-     */
-    private ConcurrentHashMap<RemoteCacheManager, RemoteCacheManager> managers;
-
-    /** Constructor for the RemoteCacheMonitor object */
-    public RemoteCacheMonitor()
-    {
-        super("JCS-RemoteCacheMonitor");
-        this.managers = new ConcurrentHashMap<>();
-        setIdlePeriod(30000L);
-    }
-
-    /**
-     * Add a manager to be monitored
-     *
-     * @param manager the remote cache manager
-     */
-    public void addManager(RemoteCacheManager manager)
-    {
-        this.managers.put(manager, manager);
-
-        // if not yet started, go ahead
-        if (this.getState() == Thread.State.NEW)
-        {
-            this.start();
-        }
-    }
-
-    /**
-     * Clean up all resources before shutdown
-     */
-    @Override
-    public void dispose()
-    {
-        this.managers.clear();
-    }
-
-    // Avoid the use of any synchronization in the process of monitoring for
-    // performance reason.
-    // If exception is thrown owing to synchronization,
-    // just skip the monitoring until the next round.
-    /** Main processing method for the RemoteCacheMonitor object */
-    @Override
-    public void doWork()
-    {
-        // Monitor each RemoteCacheManager instance one after the other.
-        // Each RemoteCacheManager corresponds to one remote connection.
-        for (RemoteCacheManager mgr : managers.values())
-        {
-            // If we can't fix them, just skip and re-try in
-            // the next round.
-            if ( mgr.canFixCaches() )
-            {
-                mgr.fixCaches();
-            }
-            else
-            {
-                allright.set(false);
-            }
-        }
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheNoWait.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheNoWait.java
deleted file mode 100644
index 8e13786..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheNoWait.java
+++ /dev/null
@@ -1,517 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.rmi.UnmarshalException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.commons.jcs.auxiliary.AbstractAuxiliaryCache;
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes;
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheClient;
-import org.apache.commons.jcs.engine.CacheAdaptor;
-import org.apache.commons.jcs.engine.CacheEventQueueFactory;
-import org.apache.commons.jcs.engine.CacheStatus;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheEventQueue;
-import org.apache.commons.jcs.engine.behavior.ICacheServiceNonLocal;
-import org.apache.commons.jcs.engine.stats.StatElement;
-import org.apache.commons.jcs.engine.stats.Stats;
-import org.apache.commons.jcs.engine.stats.behavior.IStatElement;
-import org.apache.commons.jcs.engine.stats.behavior.IStats;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * The RemoteCacheNoWait wraps the RemoteCacheClient. The client holds a handle on the
- * RemoteCacheService.
- * <p>
- * Used to queue up update requests to the underlying cache. These requests will be processed in
- * their order of arrival via the cache event queue processor.
- * <p>
- * Typically errors will be handled down stream. We only need to kill the queue if an error makes it
- * to this level from the queue. That can only happen if the queue is damaged, since the events are
- * Processed asynchronously.
- * <p>
- * There is no reason to create a queue on startup if the remote is not healthy.
- * <p>
- * If the remote cache encounters an error it will zombie--create a balking facade for the service.
- * The Zombie will queue up items until the connection is restored. An alternative way to accomplish
- * the same thing would be to stop, not destroy the queue at this level. That way items would be
- * added to the queue and then when the connection is restored, we could start the worker threads
- * again. This is a better long term solution, but it requires some significant changes to the
- * complicated worker queues.
- */
-public class RemoteCacheNoWait<K, V>
-    extends AbstractAuxiliaryCache<K, V>
-{
-    /** log instance */
-    private static final Log log = LogManager.getLog( RemoteCacheNoWait.class );
-
-    /** The remote cache client */
-    private final IRemoteCacheClient<K, V> remoteCacheClient;
-
-    /** Event queue for queuing up calls like put and remove. */
-    private ICacheEventQueue<K, V> cacheEventQueue;
-
-    /** how many times get has been called. */
-    private int getCount = 0;
-
-    /** how many times getMatching has been called. */
-    private int getMatchingCount = 0;
-
-    /** how many times getMultiple has been called. */
-    private int getMultipleCount = 0;
-
-    /** how many times remove has been called. */
-    private int removeCount = 0;
-
-    /** how many times put has been called. */
-    private int putCount = 0;
-
-    /**
-     * Constructs with the given remote cache, and fires up an event queue for asynchronous
-     * processing.
-     * <p>
-     * @param cache
-     */
-    public RemoteCacheNoWait( IRemoteCacheClient<K, V> cache )
-    {
-        remoteCacheClient = cache;
-        this.cacheEventQueue = createCacheEventQueue(cache);
-
-        if ( remoteCacheClient.getStatus() == CacheStatus.ERROR )
-        {
-            cacheEventQueue.destroy();
-        }
-    }
-
-    /**
-     * Create a cache event queue from the parameters of the remote client
-     * @param client the remote client
-     */
-    private ICacheEventQueue<K, V> createCacheEventQueue( IRemoteCacheClient<K, V> client )
-    {
-        CacheEventQueueFactory<K, V> factory = new CacheEventQueueFactory<>();
-        ICacheEventQueue<K, V> ceq = factory.createCacheEventQueue(
-            new CacheAdaptor<>( client ),
-            client.getListenerId(),
-            client.getCacheName(),
-            client.getAuxiliaryCacheAttributes().getEventQueuePoolName(),
-            client.getAuxiliaryCacheAttributes().getEventQueueType() );
-        return ceq;
-    }
-
-    /**
-     * Adds a put event to the queue.
-     * <p>
-     * @param element
-     * @throws IOException
-     */
-    @Override
-    public void update( ICacheElement<K, V> element )
-        throws IOException
-    {
-        putCount++;
-        try
-        {
-            cacheEventQueue.addPutEvent( element );
-        }
-        catch ( IOException e )
-        {
-            log.error( "Problem adding putEvent to queue.", e );
-            cacheEventQueue.destroy();
-            throw e;
-        }
-    }
-
-    /**
-     * Synchronously reads from the remote cache.
-     * <p>
-     * @param key
-     * @return element from the remote cache, or null if not present
-     * @throws IOException
-     */
-    @Override
-    public ICacheElement<K, V> get( K key )
-        throws IOException
-    {
-        getCount++;
-        try
-        {
-            return remoteCacheClient.get( key );
-        }
-        catch ( UnmarshalException ue )
-        {
-            log.debug( "Retrying the get owing to UnmarshalException." );
-
-            try
-            {
-                return remoteCacheClient.get( key );
-            }
-            catch ( IOException ex )
-            {
-                log.info( "Failed in retrying the get for the second time. ", ex );
-            }
-        }
-        catch ( IOException ex )
-        {
-            // We don't want to destroy the queue on a get failure.
-            // The RemoteCache will Zombie and queue.
-            // Since get does not use the queue, I don't want to kill the queue.
-            throw ex;
-        }
-
-        return null;
-    }
-
-    /**
-     * @param pattern
-     * @return Map
-     * @throws IOException
-     *
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMatching( String pattern )
-        throws IOException
-    {
-        getMatchingCount++;
-        try
-        {
-            return remoteCacheClient.getMatching( pattern );
-        }
-        catch ( UnmarshalException ue )
-        {
-            log.debug( "Retrying the getMatching owing to UnmarshalException." );
-
-            try
-            {
-                return remoteCacheClient.getMatching( pattern );
-            }
-            catch ( IOException ex )
-            {
-                log.info( "Failed in retrying the getMatching for the second time.", ex );
-            }
-        }
-        catch ( IOException ex )
-        {
-            // We don't want to destroy the queue on a get failure.
-            // The RemoteCache will Zombie and queue.
-            // Since get does not use the queue, I don't want to kill the queue.
-            throw ex;
-        }
-
-        return Collections.emptyMap();
-    }
-
-    /**
-     * Gets multiple items from the cache based on the given set of keys. Sends the getMultiple
-     * request on to the server rather than looping through the requested keys.
-     * <p>
-     * @param keys
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache for any of these keys
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMultiple( Set<K> keys )
-        throws IOException
-    {
-        getMultipleCount++;
-        try
-        {
-            return remoteCacheClient.getMultiple( keys );
-        }
-        catch ( UnmarshalException ue )
-        {
-            log.debug( "Retrying the getMultiple owing to UnmarshalException..." );
-
-            try
-            {
-                return remoteCacheClient.getMultiple( keys );
-            }
-            catch ( IOException ex )
-            {
-                log.info( "Failed in retrying the getMultiple for the second time.", ex );
-            }
-        }
-        catch ( IOException ex )
-        {
-            // We don't want to destroy the queue on a get failure.
-            // The RemoteCache will Zombie and queue.
-            // Since get does not use the queue, I don't want to kill the queue.
-            throw ex;
-        }
-
-        return new HashMap<>();
-    }
-
-    /**
-     * Return the keys in this cache.
-     * <p>
-     * @see org.apache.commons.jcs.auxiliary.AuxiliaryCache#getKeySet()
-     */
-    @Override
-    public Set<K> getKeySet() throws IOException
-    {
-        return remoteCacheClient.getKeySet();
-    }
-
-    /**
-     * Adds a remove request to the remote cache.
-     * <p>
-     * @param key
-     * @return if this was successful
-     * @throws IOException
-     */
-    @Override
-    public boolean remove( K key )
-        throws IOException
-    {
-        removeCount++;
-        try
-        {
-            cacheEventQueue.addRemoveEvent( key );
-        }
-        catch ( IOException e )
-        {
-            log.error( "Problem adding RemoveEvent to queue.", e );
-            cacheEventQueue.destroy();
-            throw e;
-        }
-        return false;
-    }
-
-    /**
-     * Adds a removeAll request to the remote cache.
-     * <p>
-     * @throws IOException
-     */
-    @Override
-    public void removeAll()
-        throws IOException
-    {
-        try
-        {
-            cacheEventQueue.addRemoveAllEvent();
-        }
-        catch ( IOException e )
-        {
-            log.error( "Problem adding RemoveAllEvent to queue.", e );
-            cacheEventQueue.destroy();
-            throw e;
-        }
-    }
-
-    /** Adds a dispose request to the remote cache. */
-    @Override
-    public void dispose()
-    {
-        try
-        {
-            cacheEventQueue.addDisposeEvent();
-        }
-        catch ( IOException e )
-        {
-            log.error( "Problem adding DisposeEvent to queue.", e );
-            cacheEventQueue.destroy();
-        }
-    }
-
-    /**
-     * No remote invocation.
-     * <p>
-     * @return The size value
-     */
-    @Override
-    public int getSize()
-    {
-        return remoteCacheClient.getSize();
-    }
-
-    /**
-     * No remote invocation.
-     * <p>
-     * @return The cacheType value
-     */
-    @Override
-    public CacheType getCacheType()
-    {
-        return CacheType.REMOTE_CACHE;
-    }
-
-    /**
-     * Returns the asyn cache status. An error status indicates either the remote connection is not
-     * available, or the asyn queue has been unexpectedly destroyed. No remote invocation.
-     * <p>
-     * @return The status value
-     */
-    @Override
-    public CacheStatus getStatus()
-    {
-        return cacheEventQueue.isWorking() ? remoteCacheClient.getStatus() : CacheStatus.ERROR;
-    }
-
-    /**
-     * Gets the cacheName attribute of the RemoteCacheNoWait object
-     * <p>
-     * @return The cacheName value
-     */
-    @Override
-    public String getCacheName()
-    {
-        return remoteCacheClient.getCacheName();
-    }
-
-    /**
-     * Replaces the remote cache service handle with the given handle and reset the event queue by
-     * starting up a new instance.
-     * <p>
-     * @param remote
-     */
-    public void fixCache( ICacheServiceNonLocal<?, ?> remote )
-    {
-        remoteCacheClient.fixCache( remote );
-        resetEventQ();
-    }
-
-    /**
-     * Resets the event q by first destroying the existing one and starting up new one.
-     * <p>
-     * There may be no good reason to kill the existing queue. We will sometimes need to set a new
-     * listener id, so we should create a new queue. We should let the old queue drain. If we were
-     * Connected to the failover, it would be best to finish sending items.
-     */
-    public void resetEventQ()
-    {
-        ICacheEventQueue<K, V> previousQueue = cacheEventQueue;
-
-        this.cacheEventQueue = createCacheEventQueue(this.remoteCacheClient);
-
-        if ( previousQueue.isWorking() )
-        {
-            // we don't expect anything, it would have all gone to the zombie
-            log.info( "resetEventQ, previous queue has [{0}] items queued up.",
-                    () -> previousQueue.size() );
-            previousQueue.destroy();
-        }
-    }
-
-    /**
-     * This is temporary. It allows the manager to get the lister.
-     * <p>
-     * @return the instance of the remote cache client used by this object
-     */
-    protected IRemoteCacheClient<K, V> getRemoteCache()
-    {
-        return remoteCacheClient;
-    }
-
-    /**
-     * @return Returns the AuxiliaryCacheAttributes.
-     */
-    @Override
-    public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
-    {
-        return remoteCacheClient.getAuxiliaryCacheAttributes();
-    }
-
-    /**
-     * This is for testing only. It allows you to take a look at the event queue.
-     * <p>
-     * @return ICacheEventQueue
-     */
-    protected ICacheEventQueue<K, V> getCacheEventQueue()
-    {
-        return this.cacheEventQueue;
-    }
-
-    /**
-     * Returns the stats and the cache.toString().
-     * <p>
-     * @see java.lang.Object#toString()
-     */
-    @Override
-    public String toString()
-    {
-        return getStats() + "\n" + remoteCacheClient.toString();
-    }
-
-    /**
-     * Returns the statistics in String form.
-     * <p>
-     * @return String
-     */
-    @Override
-    public String getStats()
-    {
-        return getStatistics().toString();
-    }
-
-    /**
-     * @return statistics about this communication
-     */
-    @Override
-    public IStats getStatistics()
-    {
-        IStats stats = new Stats();
-        stats.setTypeName( "Remote Cache No Wait" );
-
-        ArrayList<IStatElement<?>> elems = new ArrayList<>();
-
-        elems.add(new StatElement<>( "Status", getStatus() ) );
-
-        // get the stats from the cache queue too
-        IStats cStats = this.remoteCacheClient.getStatistics();
-        if ( cStats != null )
-        {
-            elems.addAll(cStats.getStatElements());
-        }
-
-        // get the stats from the event queue too
-        IStats eqStats = this.cacheEventQueue.getStatistics();
-        elems.addAll(eqStats.getStatElements());
-
-        elems.add(new StatElement<>( "Get Count", Integer.valueOf(this.getCount) ) );
-        elems.add(new StatElement<>( "GetMatching Count", Integer.valueOf(this.getMatchingCount) ) );
-        elems.add(new StatElement<>( "GetMultiple Count", Integer.valueOf(this.getMultipleCount) ) );
-        elems.add(new StatElement<>( "Remove Count", Integer.valueOf(this.removeCount) ) );
-        elems.add(new StatElement<>( "Put Count", Integer.valueOf(this.putCount) ) );
-
-        stats.setStatElements( elems );
-
-        return stats;
-    }
-
-    /**
-     * this won't be called since we don't do ICache logging here.
-     * <p>
-     * @return String
-     */
-    @Override
-    public String getEventLoggingExtraInfo()
-    {
-        return "Remote Cache No Wait";
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheNoWaitFacade.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheNoWaitFacade.java
deleted file mode 100644
index bdd7aa8..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheNoWaitFacade.java
+++ /dev/null
@@ -1,101 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.List;
-
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheAttributes;
-import org.apache.commons.jcs.auxiliary.remote.server.behavior.RemoteType;
-import org.apache.commons.jcs.engine.CacheStatus;
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * Used to provide access to multiple services under nowait protection. Factory should construct
- * NoWaitFacade to give to the composite cache out of caches it constructs from the varies manager
- * to lateral services.
- * <p>
- * Typically, we only connect to one remote server per facade. We use a list of one
- * RemoteCacheNoWait.
- */
-public class RemoteCacheNoWaitFacade<K, V>
-    extends AbstractRemoteCacheNoWaitFacade<K, V>
-{
-    /** log instance */
-    private static final Log log = LogManager.getLog( RemoteCacheNoWaitFacade.class );
-
-    /** Provide factory instance to RemoteCacheFailoverRunner */
-    private final RemoteCacheFactory cacheFactory;
-
-    /**
-     * Constructs with the given remote cache, and fires events to any listeners.
-     * <p>
-     * @param noWaits
-     * @param rca
-     * @param cacheEventLogger
-     * @param elementSerializer
-     * @param cacheFactory
-     */
-    public RemoteCacheNoWaitFacade( List<RemoteCacheNoWait<K,V>> noWaits,
-                                    IRemoteCacheAttributes rca,
-                                    ICacheEventLogger cacheEventLogger,
-                                    IElementSerializer elementSerializer,
-                                    RemoteCacheFactory cacheFactory)
-    {
-        super( noWaits, rca, cacheEventLogger, elementSerializer );
-        this.cacheFactory = cacheFactory;
-    }
-
-    /**
-     * Begin the failover process if this is a local cache. Clustered remote caches do not failover.
-     * <p>
-     * @param rcnw The no wait in error.
-     */
-    @Override
-    protected void failover( RemoteCacheNoWait<K, V> rcnw )
-    {
-        log.debug( "in failover for {0}", rcnw );
-
-        if ( getAuxiliaryCacheAttributes().getRemoteType() == RemoteType.LOCAL )
-        {
-            if ( rcnw.getStatus() == CacheStatus.ERROR )
-            {
-                // start failover, primary recovery process
-                RemoteCacheFailoverRunner<K, V> runner = new RemoteCacheFailoverRunner<>( this, this.cacheFactory );
-                runner.setDaemon( true );
-                runner.start();
-                runner.notifyError();
-
-                if ( getCacheEventLogger() != null )
-                {
-                    getCacheEventLogger().logApplicationEvent( "RemoteCacheNoWaitFacade", "InitiatedFailover",
-                                                               rcnw + " was in error." );
-                }
-            }
-            else
-            {
-                log.info( "The noWait is not in error" );
-            }
-        }
-    }
-
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteLocation.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteLocation.java
deleted file mode 100644
index 0ff1472..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteLocation.java
+++ /dev/null
@@ -1,144 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote;

-

-import java.util.regex.Matcher;

-import java.util.regex.Pattern;

-

-import org.apache.commons.jcs.log.Log;

-import org.apache.commons.jcs.log.LogManager;

-

-/*

- * Licensed to the Apache Software Foundation (ASF) under one

- * or more contributor license agreements.  See the NOTICE file

- * distributed with this work for additional information

- * regarding copyright ownership.  The ASF licenses this file

- * to you under the Apache License, Version 2.0 (the

- * "License"); you may not use this file except in compliance

- * with the License.  You may obtain a copy of the License at

- *

- *   http://www.apache.org/licenses/LICENSE-2.0

- *

- * Unless required by applicable law or agreed to in writing,

- * software distributed under the License is distributed on an

- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

- * KIND, either express or implied.  See the License for the

- * specific language governing permissions and limitations

- * under the License.

- */

-

-/**

- * Location of the RMI registry.

- */

-public final class RemoteLocation

-{

-    /** The logger. */

-    private static final Log log = LogManager.getLog( RemoteLocation.class );

-

-    /** Pattern for parsing server:port */

-    private static final Pattern SERVER_COLON_PORT = Pattern.compile("(\\S+)\\s*:\\s*(\\d+)");

-

-    /** Host name */

-    private final String host;

-

-    /** Port */

-    private final int port;

-

-    /**

-     * Constructor for the Location object

-     * <p>

-     * @param host

-     * @param port

-     */

-    public RemoteLocation( String host, int port )

-    {

-        this.host = host;

-        this.port = port;

-    }

-

-    /**

-     * @return the host

-     */

-    public String getHost()

-    {

-        return host;

-    }

-

-    /**

-     * @return the port

-     */

-    public int getPort()

-    {

-        return port;

-    }

-

-    /**

-     * @param obj

-     * @return true if the host and port are equal

-     */

-    @Override

-    public boolean equals( Object obj )

-    {

-        if ( obj == this )

-        {

-            return true;

-        }

-        if ( obj == null || !( obj instanceof RemoteLocation ) )

-        {

-            return false;

-        }

-        RemoteLocation l = (RemoteLocation) obj;

-        if ( this.host == null )

-        {

-            return l.host == null && port == l.port;

-        }

-        return host.equals( l.host ) && port == l.port;

-    }

-

-    /**

-     * @return int

-     */

-    @Override

-    public int hashCode()

-    {

-        return host == null ? port : host.hashCode() ^ port;

-    }

-

-    /**

-     * @see java.lang.Object#toString()

-     */

-    @Override

-    public String toString()

-    {

-        StringBuilder sb = new StringBuilder();

-        if (this.host != null)

-        {

-            sb.append(this.host);

-        }

-        sb.append(':').append(this.port);

-

-        return sb.toString();

-    }

-

-    /**

-     * Parse remote server and port from the string representation server:port and store them in

-     * a RemoteLocation object

-     *

-     * @param server the input string

-     * @return the remote location object

-     */

-    public static RemoteLocation parseServerAndPort(final String server)

-    {

-        Matcher match = SERVER_COLON_PORT.matcher(server);

-

-        if (match.find() && match.groupCount() == 2)

-        {

-            RemoteLocation location = new RemoteLocation( match.group(1), Integer.parseInt( match.group(2) ) );

-            return location;

-        }

-        else

-        {

-            log.error("Invalid server descriptor: {0}", server);

-        }

-

-        return null;

-    }

-}
\ No newline at end of file
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteUtils.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteUtils.java
deleted file mode 100644
index b3075a6..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/RemoteUtils.java
+++ /dev/null
@@ -1,264 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.InetSocketAddress;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.net.URL;
-import java.rmi.RemoteException;
-import java.rmi.registry.LocateRegistry;
-import java.rmi.registry.Registry;
-import java.rmi.server.RMISocketFactory;
-import java.util.Properties;
-
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * This class provides some basic utilities for doing things such as starting
- * the registry properly.
- */
-public class RemoteUtils
-{
-    /** The logger. */
-    private static final Log log = LogManager.getLog(RemoteUtils.class);
-
-    /** No instances please. */
-    private RemoteUtils()
-    {
-        super();
-    }
-
-    /**
-     * Creates and exports a registry on the specified port of the local host.
-     * <p>
-     *
-     * @param port
-     * @return the registry
-     */
-    public static Registry createRegistry(int port)
-    {
-        Registry registry = null;
-
-        // if ( log.isInfoEnabled() )
-        // {
-        // log.info( "createRegistry> Setting security manager" );
-        // }
-        //
-        // System.setSecurityManager( new RMISecurityManager() );
-
-        if (port < 1024)
-        {
-            log.warn("createRegistry> Port chosen was less than 1024, will use default [{0}] instead.",
-                    Registry.REGISTRY_PORT);
-            port = Registry.REGISTRY_PORT;
-        }
-
-        try
-        {
-            registry = LocateRegistry.createRegistry(port);
-            log.info("createRegistry> Created the registry on port {0}", port);
-        }
-        catch (RemoteException e)
-        {
-            log.warn("createRegistry> Problem creating registry. It may already be started.",
-                    e);
-        }
-        catch (Throwable t)
-        {
-            log.error("createRegistry> Problem creating registry.", t);
-        }
-
-        if (registry == null)
-        {
-            try
-            {
-                registry = LocateRegistry.getRegistry(port);
-            }
-            catch (RemoteException e)
-            {
-                log.error("createRegistry> Problem getting a registry reference.", e);
-            }
-        }
-
-        return registry;
-    }
-
-    /**
-     * Loads properties for the named props file.
-     * First tries class path, then file, then URL
-     * <p>
-     *
-     * @param propFile
-     * @return The properties object for the file
-     * @throws IOException
-     */
-    public static Properties loadProps(String propFile)
-            throws IOException
-    {
-        InputStream is = RemoteUtils.class.getResourceAsStream(propFile);
-
-        if (null == is) // not found in class path
-        {
-            // Try root of class path
-            if (propFile != null && !propFile.startsWith("/"))
-            {
-                is = RemoteUtils.class.getResourceAsStream("/" + propFile);
-            }
-        }
-
-        if (null == is) // not found in class path
-        {
-            if (new File(propFile).exists())
-            {
-                // file found
-                is = new FileInputStream(propFile);
-            }
-            else
-            {
-                // try URL
-                is = new URL(propFile).openStream();
-            }
-        }
-
-        Properties props = new Properties();
-        try
-        {
-            props.load(is);
-            log.debug("props.size={0}", () -> props.size());
-
-            if (log.isTraceEnabled())
-            {
-                StringBuilder buf = new StringBuilder();
-                props.forEach((key, value)
-                        -> buf.append('\n').append(key).append(" = ").append(value));
-                log.trace(buf.toString());
-            }
-
-        }
-        catch (IOException ex)
-        {
-            log.error("Error loading remote properties, for file name "
-                    + "[{0}]", propFile, ex);
-        }
-        finally
-        {
-            if (is != null)
-            {
-                is.close();
-            }
-        }
-        return props;
-    }
-
-    /**
-     * Configure a custom socket factory to set the timeout value. This sets the
-     * global socket factory. It's used only if a custom factory is not
-     * configured for the specific object.
-     * <p>
-     *
-     * @param timeoutMillis
-     */
-    public static void configureGlobalCustomSocketFactory(final int timeoutMillis)
-    {
-        try
-        {
-            // Don't set a socket factory if the setting is -1
-            if (timeoutMillis > 0)
-            {
-                log.info("RmiSocketFactoryTimeoutMillis [{0}]. "
-                    + " Configuring a custom socket factory.", timeoutMillis);
-
-                // use this socket factory to add a timeout.
-                RMISocketFactory.setSocketFactory(new RMISocketFactory()
-                {
-                    @Override
-                    public Socket createSocket(String host, int port)
-                            throws IOException
-                    {
-                        Socket socket = new Socket();
-                        socket.setSoTimeout(timeoutMillis);
-                        socket.setSoLinger(false, 0);
-                        socket.connect(new InetSocketAddress(host, port), timeoutMillis);
-                        return socket;
-                    }
-
-                    @Override
-                    public ServerSocket createServerSocket(int port)
-                            throws IOException
-                    {
-                        return new ServerSocket(port);
-                    }
-                });
-            }
-        }
-        catch (IOException e)
-        {
-            // Only try to do it once. Otherwise we
-            // Generate errors for each region on construction.
-            RMISocketFactory factoryInUse = RMISocketFactory.getSocketFactory();
-            if (factoryInUse != null && !factoryInUse.getClass().getName().startsWith("org.apache.commons.jcs"))
-            {
-                log.info("Could not create new custom socket factory. {0} Factory in use = {1}",
-                        () -> e.getMessage(), () -> RMISocketFactory.getSocketFactory());
-            }
-        }
-    }
-
-    /**
-     * Get the naming url used for RMI registration
-     *
-     * @param location
-     *            the remote location
-     * @param serviceName
-     *            the remote service name
-     * @return the URL for RMI lookup
-     */
-    public static String getNamingURL(final RemoteLocation location, final String serviceName)
-    {
-        return getNamingURL(location.getHost(), location.getPort(), serviceName);
-    }
-
-    /**
-     * Get the naming url used for RMI registration
-     *
-     * @param registryHost
-     *            the remote host
-     * @param registryPort
-     *            the remote port
-     * @param serviceName
-     *            the remote service name
-     * @return the URL for RMI lookup
-     */
-    public static String getNamingURL(final String registryHost, final int registryPort, final String serviceName)
-    {
-        if (registryHost.contains(":"))
-        { // TODO improve this check? See also JCS-133
-            return "//[" + registryHost.replaceFirst("%", "%25") + "]:" + registryPort + "/" + serviceName;
-        }
-        final String registryURL = "//" + registryHost + ":" + registryPort + "/" + serviceName;
-        return registryURL;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/behavior/ICommonRemoteCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/behavior/ICommonRemoteCacheAttributes.java
deleted file mode 100644
index 2b1e20e..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/behavior/ICommonRemoteCacheAttributes.java
+++ /dev/null
@@ -1,172 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes;
-import org.apache.commons.jcs.auxiliary.remote.RemoteLocation;
-import org.apache.commons.jcs.auxiliary.remote.server.behavior.RemoteType;
-
-/**
- * This specifies what a remote cache configuration object should look like.
- */
-public interface ICommonRemoteCacheAttributes
-    extends AuxiliaryCacheAttributes
-{
-    /** The default timeout for the custom RMI socket factory */
-    int DEFAULT_RMI_SOCKET_FACTORY_TIMEOUT_MILLIS = 10000;
-
-    /**
-     * Gets the remoteTypeName attribute of the IRemoteCacheAttributes object
-     * <p>
-     * @return The remoteTypeName value
-     */
-    String getRemoteTypeName();
-
-    /**
-     * Sets the remoteTypeName attribute of the IRemoteCacheAttributes object
-     * <p>
-     * @param s The new remoteTypeName value
-     */
-    void setRemoteTypeName( String s );
-
-    /**
-     * Gets the remoteType attribute of the IRemoteCacheAttributes object
-     * <p>
-     * @return The remoteType value
-     */
-    RemoteType getRemoteType();
-
-    /**
-     * Sets the remoteType attribute of the IRemoteCacheAttributes object
-     * <p>
-     * @param p The new remoteType value
-     */
-    void setRemoteType( RemoteType p );
-
-    /**
-     * Gets the remoteServiceName attribute of the IRemoteCacheAttributes object
-     * <p>
-     * @return The remoteServiceName value
-     */
-    String getRemoteServiceName();
-
-    /**
-     * Sets the remoteServiceName attribute of the IRemoteCacheAttributes object
-     * <p>
-     * @param s The new remoteServiceName value
-     */
-    void setRemoteServiceName( String s );
-
-    /**
-     * Sets the location attribute of the RemoteCacheAttributes object.
-     * <p>
-     * @param location The new location value
-     */
-    void setRemoteLocation( RemoteLocation location );
-
-    /**
-     * Sets the location attribute of the RemoteCacheAttributes object.
-     * <p>
-     * @param host The new remoteHost value
-     * @param port The new remotePort value
-     */
-    void setRemoteLocation( String host, int port );
-
-    /**
-     * Gets the location attribute of the RemoteCacheAttributes object.
-     * <p>
-     * @return The remote location value
-     */
-    public RemoteLocation getRemoteLocation();
-
-    /**
-     * Gets the clusterServers attribute of the IRemoteCacheAttributes object
-     * <p>
-     * @return The clusterServers value
-     */
-    String getClusterServers();
-
-    /**
-     * Sets the clusterServers attribute of the IRemoteCacheAttributes object
-     * <p>
-     * @param s The new clusterServers value
-     */
-    void setClusterServers( String s );
-
-    /**
-     * Gets the removeUponRemotePut attribute of the IRemoteCacheAttributes object
-     * <p>
-     * @return The removeUponRemotePut value
-     */
-    boolean getRemoveUponRemotePut();
-
-    /**
-     * Sets the removeUponRemotePut attribute of the IRemoteCacheAttributes object
-     * <p>
-     * @param r The new removeUponRemotePut value
-     */
-    void setRemoveUponRemotePut( boolean r );
-
-    /**
-     * Gets the getOnly attribute of the IRemoteCacheAttributes object
-     * <p>
-     * @return The getOnly value
-     */
-    boolean getGetOnly();
-
-    /**
-     * Sets the getOnly attribute of the IRemoteCacheAttributes object
-     * <p>
-     * @param r The new getOnly value
-     */
-    void setGetOnly( boolean r );
-
-    /**
-     * Should cluster updates be propagated to the locals
-     * <p>
-     * @return The localClusterConsistency value
-     */
-    boolean isLocalClusterConsistency();
-
-    /**
-     * Should cluster updates be propagated to the locals
-     * <p>
-     * @param r The new localClusterConsistency value
-     */
-    void setLocalClusterConsistency( boolean r );
-
-    /**
-     * This sets a general timeout on the rmi socket factory. By default the socket factory will
-     * block forever.
-     * <p>
-     * We have a default setting. The default rmi behavior should never be used.
-     * <p>
-     * @return int milliseconds
-     */
-    int getRmiSocketFactoryTimeoutMillis();
-
-    /**
-     * This sets a general timeout on the RMI socket factory. By default the socket factory will
-     * block forever.
-     * <p>
-     * @param rmiSocketFactoryTimeoutMillis
-     */
-    void setRmiSocketFactoryTimeoutMillis( int rmiSocketFactoryTimeoutMillis );
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/behavior/IRemoteCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/behavior/IRemoteCacheAttributes.java
deleted file mode 100644
index aa528ba..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/behavior/IRemoteCacheAttributes.java
+++ /dev/null
@@ -1,182 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.behavior;
-
-import java.util.List;
-
-import org.apache.commons.jcs.auxiliary.remote.RemoteLocation;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * This specifies what a remote cache configuration object should look like.
- */
-public interface IRemoteCacheAttributes
-    extends ICommonRemoteCacheAttributes
-{
-    /**
-     * If RECEIVE is false then the remote cache will not register a listener with the remote
-     * server. This allows you to configure a remote server as a repository from which you can get
-     * and to which you put, but from which you do not receive any notifications. That is, you will
-     * not receive updates or removes.
-     * <p>
-     * If you set this option to false, you should set your local memory size to 0.
-     */
-    boolean DEFAULT_RECEIVE = true;
-
-    /**
-     * The number of elements the zombie queue will hold. This queue is used to store events if we
-     * loose our connection with the server.
-     */
-    int DEFAULT_ZOMBIE_QUEUE_MAX_SIZE = 1000;
-
-    /**
-     * Gets the failoverIndex attribute of the IRemoteCacheAttributes object.
-     * <p>
-     * This specifies which server in the list we are listening to if the number is greater than 0
-     * we will try to move to 0 position the primary is added as position 1 if it is present
-     * <p>
-     * @return The failoverIndex value
-     */
-    int getFailoverIndex();
-
-    /**
-     * Sets the failoverIndex attribute of the IRemoteCacheAttributes object
-     * <p>
-     * @param p The new failoverIndex value
-     */
-    void setFailoverIndex( int p );
-
-    /**
-     * Gets the failovers attribute of the IRemoteCacheAttributes object
-     * <p>
-     * @return The failovers value
-     */
-    List<RemoteLocation> getFailovers();
-
-    /**
-     * Sets the failovers attribute of the IRemoteCacheAttributes object
-     * <p>
-     * @param failovers The new failovers value
-     */
-    void setFailovers( List<RemoteLocation> failovers );
-
-    /**
-     * Gets the localPort attribute of the IRemoteCacheAttributes object
-     * <p>
-     * @return The localPort value
-     */
-    int getLocalPort();
-
-    /**
-     * Sets the localPort attribute of the IRemoteCacheAttributes object
-     * <p>
-     * @param p The new localPort value
-     */
-    void setLocalPort( int p );
-
-    /**
-     * Gets the failoverServers attribute of the IRemoteCacheAttributes object
-     * <p>
-     * @return The failoverServers value
-     */
-    String getFailoverServers();
-
-    /**
-     * Sets the failoverServers attribute of the IRemoteCacheAttributes object
-     * <p>
-     * @param s The new failoverServers value
-     */
-    void setFailoverServers( String s );
-
-    /**
-     * The thread pool the remote cache should use. At first this will only be for gets.
-     * <p>
-     * The default name is "remote_cache_client"
-     * <p>
-     * @return the name of the pool
-     */
-    String getThreadPoolName();
-
-    /**
-     * Set the name of the pool to use. Pools should be defined in the cache.ccf.
-     * <p>
-     * @param name
-     */
-    void setThreadPoolName( String name );
-
-    /**
-     * -1 and 0 mean no timeout, this is the default if the timeout is -1 or 0, no threadpool will
-     * be used.
-     * <p>
-     * @return the time in millis
-     */
-    int getGetTimeoutMillis();
-
-    /**
-     * -1 means no timeout, this is the default if the timeout is -1 or 0, no threadpool will be
-     * used. If the timeout is greater than 0 a threadpool will be used for get requests.
-     * <p>
-     * @param millis
-     */
-    void setGetTimeoutMillis( int millis );
-
-    /**
-     * By default this option is true. If you set it to false, you will not receive updates or
-     * removes from the remote server.
-     * <p>
-     * @param receive
-     */
-    void setReceive( boolean receive );
-
-    /**
-     * If RECEIVE is false then the remote cache will not register a listener with the remote
-     * server. This allows you to configure a remote server as a repository from which you can get
-     * and to which you put, but from which you do not receive any notifications. That is, you will
-     * not receive updates or removes.
-     * <p>
-     * If you set this option to false, you should set your local memory size to 0.
-     * <p>
-     * The remote cache manager uses this value to decide whether or not to register a listener.
-     * <p>
-     * It makes no sense to configure a cluster remote cache to no receive.
-     * <p>
-     * Since a non-receiving remote cache client will not register a listener, it will not have a
-     * listener id assigned from the server. As such the remote server cannot determine if it is a
-     * cluster or a normal client. It will assume that it is a normal client.
-     * <p>
-     * @return the receive value.
-     */
-    boolean isReceive();
-
-    /**
-     * The number of elements the zombie queue will hold. This queue is used to store events if we
-     * loose our connection with the server.
-     * <p>
-     * @param zombieQueueMaxSize The zombieQueueMaxSize to set.
-     */
-    void setZombieQueueMaxSize( int zombieQueueMaxSize );
-
-    /**
-     * The number of elements the zombie queue will hold. This queue is used to store events if we
-     * loose our connection with the server.
-     * <p>
-     * @return Returns the zombieQueueMaxSize.
-     */
-    int getZombieQueueMaxSize();
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/behavior/IRemoteCacheClient.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/behavior/IRemoteCacheClient.java
deleted file mode 100644
index acb68f6..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/behavior/IRemoteCacheClient.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.AuxiliaryCache;
-import org.apache.commons.jcs.engine.behavior.ICacheServiceNonLocal;
-
-/**
- * This defines the behavior expected of a remote cache client. This extends Auxiliary cache which
- * in turn extends ICache.
- * <p>
- * I'd like generalize this a bit.
- * <p>
- * @author Aaron Smuts
- */
-public interface IRemoteCacheClient<K, V>
-    extends AuxiliaryCache<K, V>
-{
-    /**
-     * Replaces the current remote cache service handle with the given handle. If the current remote
-     * is a Zombie, the propagate the events that may be queued to the restored service.
-     * <p>
-     * @param remote ICacheServiceNonLocal -- the remote server or proxy to the remote server
-     */
-    void fixCache( ICacheServiceNonLocal<?, ?> remote );
-
-    /**
-     * Gets the listenerId attribute of the RemoteCacheListener object.
-     * <p>
-     * All requests to the remote cache must include a listener id. This allows the server to avoid
-     * sending updates the the listener associated with this client.
-     * <p>
-     * @return The listenerId value
-     */
-    long getListenerId();
-
-    /**
-     * This returns the listener associated with this remote cache. TODO we should try to get this
-     * out of the interface.
-     * <p>
-     * @return IRemoteCacheListener
-     */
-    IRemoteCacheListener<K, V> getListener();
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/behavior/IRemoteCacheConstants.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/behavior/IRemoteCacheConstants.java
deleted file mode 100644
index 0dd06a9..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/behavior/IRemoteCacheConstants.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.behavior.ICacheServiceNonLocal;
-
-
-/**
- * This holds constants that are used by the remote cache.
- */
-public interface IRemoteCacheConstants
-{
-    /** Mapping to props file value */
-    String REMOTE_CACHE_SERVICE_VAL = ICacheServiceNonLocal.class.getName();
-
-    /** The prefix for cache server config. */
-    String CACHE_SERVER_PREFIX = "jcs.remotecache";
-
-    /**
-     * I'm trying to migrate everything to use this prefix. All those below will be replaced. Any of
-     * the RemoteCacheServerAttributes can be configured this way.
-     */
-    String CACHE_SERVER_ATTRIBUTES_PROPERTY_PREFIX = CACHE_SERVER_PREFIX + ".serverattributes";
-
-    /**
-     * This is the name of the class that will be used for an object specific socket factory.
-     */
-    String CUSTOM_RMI_SOCKET_FACTORY_PROPERTY_PREFIX = CACHE_SERVER_PREFIX + ".customrmisocketfactory";
-
-    /** Property prefix, should be jcs.remote but this would break existing config. */
-    String PROPERTY_PREFIX = "remote";
-
-    /** Mapping to props file value */
-    String SOCKET_TIMEOUT_MILLIS = PROPERTY_PREFIX + ".cache.rmiSocketFactoryTimeoutMillis";
-
-    /** Mapping to props file value */
-    String REMOTE_CACHE_SERVICE_NAME = PROPERTY_PREFIX + ".cache.service.name";
-
-    /** Mapping to props file value */
-    String TOMCAT_XML = PROPERTY_PREFIX + ".tomcat.xml";
-
-    /** Mapping to props file value */
-    String TOMCAT_ON = PROPERTY_PREFIX + ".tomcat.on";
-
-    /** Mapping to props file value */
-    String REMOTE_CACHE_SERVICE_PORT = PROPERTY_PREFIX + ".cache.service.port";
-
-    /** Mapping to props file value */
-    String REMOTE_LOCAL_CLUSTER_CONSISTENCY = PROPERTY_PREFIX + ".cluster.LocalClusterConsistency";
-
-    /** Mapping to props file value */
-    String REMOTE_ALLOW_CLUSTER_GET = PROPERTY_PREFIX + ".cluster.AllowClusterGet";
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/behavior/IRemoteCacheDispatcher.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/behavior/IRemoteCacheDispatcher.java
deleted file mode 100644
index c5d0b0d..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/behavior/IRemoteCacheDispatcher.java
+++ /dev/null
@@ -1,46 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.remote.value.RemoteCacheRequest;
-import org.apache.commons.jcs.auxiliary.remote.value.RemoteCacheResponse;
-
-import java.io.IOException;
-
-/**
- * In the future, this can be used as a generic dispatcher abstraction.
- * <p>
- * At the time of creation, only the http remote cache uses it. The RMI remote could be converted to
- * use it as well.
- */
-public interface IRemoteCacheDispatcher
-{
-    /**
-     * All requests will go through this method. The dispatcher implementation will send the request
-     * remotely.
-     * <p>
-     * @param remoteCacheRequest
-     * @return RemoteCacheResponse
-     * @throws IOException
-     */
-    <K, V, T>
-        RemoteCacheResponse<T> dispatchRequest( RemoteCacheRequest<K, V> remoteCacheRequest )
-            throws IOException;
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/behavior/IRemoteCacheListener.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/behavior/IRemoteCacheListener.java
deleted file mode 100644
index b673383..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/behavior/IRemoteCacheListener.java
+++ /dev/null
@@ -1,81 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.remote.server.behavior.RemoteType;
-import org.apache.commons.jcs.engine.behavior.ICacheListener;
-
-import java.io.IOException;
-import java.rmi.Remote;
-
-/**
- * Listens for remote cache event notification ( rmi callback ).
- */
-public interface IRemoteCacheListener<K, V>
-    extends ICacheListener<K, V>, Remote
-{
-    /**
-     * Get the id to be used by this manager.
-     * <p>
-     * @return long
-     * @throws IOException
-     */
-    @Override
-    long getListenerId()
-        throws IOException;
-
-    /**
-     * Set the id to be used by this manager. The remote cache server identifies clients by this id.
-     * The value will be set by the server through the remote cache listener.
-     * <p>
-     * @param id
-     * @throws IOException
-     */
-    @Override
-    void setListenerId( long id )
-        throws IOException;
-
-    /**
-     * Gets the remoteType attribute of the IRemoteCacheListener object
-     * <p>
-     * @return The remoteType value
-     * @throws IOException
-     */
-    RemoteType getRemoteType()
-        throws IOException;
-
-    /**
-     * This is for debugging. It allows the remote cache server to log the address of any listeners
-     * that register.
-     * <p>
-     * @return the local host address.
-     * @throws IOException
-     */
-    String getLocalHostAddress()
-        throws IOException;
-
-    /**
-     * Deregisters itself.
-     * <p>
-     * @throws IOException
-     */
-    void dispose()
-        throws IOException;
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/behavior/IRemoteHttpCacheConstants.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/behavior/IRemoteHttpCacheConstants.java
deleted file mode 100644
index 49515f3..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/behavior/IRemoteHttpCacheConstants.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.http.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/** Constants used throughout the HTTP remote cache. */
-public interface IRemoteHttpCacheConstants
-{
-    /** The prefix for cache server config. */
-    String HTTP_CACHE_SERVER_PREFIX = "jcs.remotehttpcache";
-
-    /** All of the RemoteHttpCacheServiceAttributes can be configured this way. */
-    String HTTP_CACHE_SERVER_ATTRIBUTES_PROPERTY_PREFIX = HTTP_CACHE_SERVER_PREFIX
-        + ".serverattributes";
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/client/AbstractHttpClient.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/client/AbstractHttpClient.java
deleted file mode 100644
index 5b86fe1..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/client/AbstractHttpClient.java
+++ /dev/null
@@ -1,154 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.http.client;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-import org.apache.http.HttpResponse;
-import org.apache.http.HttpVersion;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.config.CookieSpecs;
-import org.apache.http.client.config.RequestConfig;
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.client.methods.RequestBuilder;
-import org.apache.http.impl.client.HttpClientBuilder;
-
-/**
- * This class simply configures the http multithreaded connection manager.
- * <p>
- * This is abstract because it can do anything. Child classes can overwrite whatever they want.
- */
-public abstract class AbstractHttpClient
-{
-    /** The client */
-    private HttpClient httpClient;
-
-    /** The protocol version */
-    private HttpVersion httpVersion;
-
-    /** Configuration settings. */
-    private RemoteHttpCacheAttributes remoteHttpCacheAttributes;
-
-    /** The Logger. */
-    private static final Log log = LogManager.getLog( AbstractHttpClient.class );
-
-    /**
-     * Sets the default Properties File and Heading, and creates the HttpClient and connection
-     * manager.
-     * <p>
-     * @param remoteHttpCacheAttributes
-     */
-    public AbstractHttpClient( RemoteHttpCacheAttributes remoteHttpCacheAttributes )
-    {
-        this.remoteHttpCacheAttributes = remoteHttpCacheAttributes;
-
-        String httpVersion = getRemoteHttpCacheAttributes().getHttpVersion();
-        if ( "1.1".equals( httpVersion ) )
-        {
-            this.httpVersion = HttpVersion.HTTP_1_1;
-        }
-        else if ( "1.0".equals( httpVersion ) )
-        {
-            this.httpVersion = HttpVersion.HTTP_1_0;
-        }
-        else
-        {
-            log.warn( "Unrecognized value for 'httpVersion': [{0}], defaulting to 1.1",
-                    httpVersion );
-            this.httpVersion = HttpVersion.HTTP_1_1;
-        }
-
-        HttpClientBuilder builder = HttpClientBuilder.create();
-        configureClient(builder);
-        this.httpClient = builder.build();
-    }
-
-    /**
-     * Configures the http client.
-     *
-     * @param builder client builder to configure
-     */
-    protected void configureClient(HttpClientBuilder builder)
-    {
-        if ( getRemoteHttpCacheAttributes().getMaxConnectionsPerHost() > 0 )
-        {
-            builder.setMaxConnTotal(getRemoteHttpCacheAttributes().getMaxConnectionsPerHost());
-            builder.setMaxConnPerRoute(getRemoteHttpCacheAttributes().getMaxConnectionsPerHost());
-        }
-
-        builder.setDefaultRequestConfig(RequestConfig.custom()
-                .setConnectTimeout(getRemoteHttpCacheAttributes().getConnectionTimeoutMillis())
-                .setSocketTimeout(getRemoteHttpCacheAttributes().getSocketTimeoutMillis())
-                // By default we instruct HttpClient to ignore cookies.
-                .setCookieSpec(CookieSpecs.IGNORE_COOKIES)
-                .build());
-    }
-
-    /**
-     * Execute the web service call
-     * <p>
-     * @param builder builder for the post request
-     *
-     * @return the call response
-     *
-     * @throws IOException on i/o error
-     */
-    protected final HttpResponse doWebserviceCall( RequestBuilder builder )
-        throws IOException
-    {
-        preProcessWebserviceCall( builder.setVersion(httpVersion) );
-        HttpUriRequest request = builder.build();
-        HttpResponse httpResponse = this.httpClient.execute( request );
-        postProcessWebserviceCall( request, httpResponse );
-
-        return httpResponse;
-    }
-
-    /**
-     * Called before the execute call on the client.
-     * <p>
-     * @param requestBuilder http method request builder
-     *
-     * @throws IOException
-     */
-    protected abstract void preProcessWebserviceCall( RequestBuilder requestBuilder )
-        throws IOException;
-
-    /**
-     * Called after the execute call on the client.
-     * <p>
-     * @param request http request
-     * @param httpState result of execution
-     *
-     * @throws IOException
-     */
-    protected abstract void postProcessWebserviceCall( HttpUriRequest request, HttpResponse httpState )
-        throws IOException;
-
-    /**
-     * @return the remoteHttpCacheAttributes
-     */
-    protected RemoteHttpCacheAttributes getRemoteHttpCacheAttributes()
-    {
-        return remoteHttpCacheAttributes;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/client/RemoteHttpCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/client/RemoteHttpCache.java
deleted file mode 100644
index 8f884f8..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/client/RemoteHttpCache.java
+++ /dev/null
@@ -1,113 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.http.client;
-
-import java.io.IOException;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.remote.AbstractRemoteAuxiliaryCache;
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheListener;
-import org.apache.commons.jcs.engine.ZombieCacheServiceNonLocal;
-import org.apache.commons.jcs.engine.behavior.ICacheServiceNonLocal;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * This uses an http client as the service.
- */
-public class RemoteHttpCache<K, V>
-    extends AbstractRemoteAuxiliaryCache<K, V>
-{
-    /** The logger. */
-    private static final Log log = LogManager.getLog( RemoteHttpCache.class );
-
-    /** for error notifications */
-    private RemoteHttpCacheMonitor monitor;
-
-    /** Keep the child copy here for the restore process. */
-    private RemoteHttpCacheAttributes remoteHttpCacheAttributes;
-
-    /**
-     * Constructor for the RemoteCache object. This object communicates with a remote cache server.
-     * One of these exists for each region. This also holds a reference to a listener. The same
-     * listener is used for all regions for one remote server. Holding a reference to the listener
-     * allows this object to know the listener id assigned by the remote cache.
-     * <p>
-     * @param remoteHttpCacheAttributes
-     * @param remote
-     * @param listener
-     * @param monitor the cache monitor
-     */
-    public RemoteHttpCache( RemoteHttpCacheAttributes remoteHttpCacheAttributes, ICacheServiceNonLocal<K, V> remote,
-                            IRemoteCacheListener<K, V> listener, RemoteHttpCacheMonitor monitor )
-    {
-        super( remoteHttpCacheAttributes, remote, listener );
-
-        this.remoteHttpCacheAttributes = remoteHttpCacheAttributes;
-        this.monitor = monitor;
-    }
-
-    /**
-     * Nothing right now. This should setup a zombie and initiate recovery.
-     * <p>
-     * @param ex
-     * @param msg
-     * @param eventName
-     * @throws IOException
-     */
-    @Override
-    protected void handleException( Exception ex, String msg, String eventName )
-        throws IOException
-    {
-        // we should not switch if the existing is a zombie.
-        if ( !( getRemoteCacheService() instanceof ZombieCacheServiceNonLocal ) )
-        {
-            String message = "Disabling remote cache due to error: " + msg;
-            logError( cacheName, "", message );
-            log.error( message, ex );
-
-            setRemoteCacheService( new ZombieCacheServiceNonLocal<>( getRemoteCacheAttributes().getZombieQueueMaxSize() ) );
-
-            monitor.notifyError( this );
-        }
-
-        if ( ex instanceof IOException )
-        {
-            throw (IOException) ex;
-        }
-        throw new IOException( ex.getMessage() );
-    }
-
-    /**
-     * @return url of service
-     */
-    @Override
-    public String getEventLoggingExtraInfo()
-    {
-        return null;
-    }
-
-    /**
-     * @return the remoteHttpCacheAttributes
-     */
-    public RemoteHttpCacheAttributes getRemoteHttpCacheAttributes()
-    {
-        return remoteHttpCacheAttributes;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/client/RemoteHttpCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/client/RemoteHttpCacheAttributes.java
deleted file mode 100644
index c68670d..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/client/RemoteHttpCacheAttributes.java
+++ /dev/null
@@ -1,228 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.http.client;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes;
-
-/** Http client specific settings. */
-public class RemoteHttpCacheAttributes
-    extends RemoteCacheAttributes
-{
-    /** Don't change. */
-    private static final long serialVersionUID = -5944327125140505212L;
-
-    /** http verison to use. */
-    private static final String DEFAULT_HTTP_VERSION = "1.1";
-
-    /** The max connections allowed per host */
-    private int maxConnectionsPerHost = 100;
-
-    /** The socket timeout. */
-    private int socketTimeoutMillis = 3000;
-
-    /** The socket connections timeout */
-    private int connectionTimeoutMillis = 5000;
-
-    /** http verison to use. */
-    private String httpVersion = DEFAULT_HTTP_VERSION;
-
-    /** The cache name will be included on the parameters */
-    private boolean includeCacheNameAsParameter = true;
-
-    /** keys and patterns will be included in the parameters */
-    private boolean includeKeysAndPatternsAsParameter = true;
-
-    /** keys and patterns will be included in the parameters */
-    private boolean includeRequestTypeasAsParameter = true;
-
-    /** The complete URL to the service. */
-    private String url;
-
-    /** The default classname for the client.  */
-    public static final String DEFAULT_REMOTE_HTTP_CLIENT_CLASS_NAME = RemoteHttpCacheClient.class.getName();
-
-    /** This allows users to inject their own client implementation. */
-    private String remoteHttpClientClassName = DEFAULT_REMOTE_HTTP_CLIENT_CLASS_NAME;
-
-    /**
-     * @param maxConnectionsPerHost the maxConnectionsPerHost to set
-     */
-    public void setMaxConnectionsPerHost( int maxConnectionsPerHost )
-    {
-        this.maxConnectionsPerHost = maxConnectionsPerHost;
-    }
-
-    /**
-     * @return the maxConnectionsPerHost
-     */
-    public int getMaxConnectionsPerHost()
-    {
-        return maxConnectionsPerHost;
-    }
-
-    /**
-     * @param socketTimeoutMillis the socketTimeoutMillis to set
-     */
-    public void setSocketTimeoutMillis( int socketTimeoutMillis )
-    {
-        this.socketTimeoutMillis = socketTimeoutMillis;
-    }
-
-    /**
-     * @return the socketTimeoutMillis
-     */
-    public int getSocketTimeoutMillis()
-    {
-        return socketTimeoutMillis;
-    }
-
-    /**
-     * @param httpVersion the httpVersion to set
-     */
-    public void setHttpVersion( String httpVersion )
-    {
-        this.httpVersion = httpVersion;
-    }
-
-    /**
-     * @return the httpVersion
-     */
-    public String getHttpVersion()
-    {
-        return httpVersion;
-    }
-
-    /**
-     * @param connectionTimeoutMillis the connectionTimeoutMillis to set
-     */
-    public void setConnectionTimeoutMillis( int connectionTimeoutMillis )
-    {
-        this.connectionTimeoutMillis = connectionTimeoutMillis;
-    }
-
-    /**
-     * @return the connectionTimeoutMillis
-     */
-    public int getConnectionTimeoutMillis()
-    {
-        return connectionTimeoutMillis;
-    }
-
-    /**
-     * @param includeCacheNameInURL the includeCacheNameInURL to set
-     */
-    public void setIncludeCacheNameAsParameter( boolean includeCacheNameInURL )
-    {
-        this.includeCacheNameAsParameter = includeCacheNameInURL;
-    }
-
-    /**
-     * @return the includeCacheNameInURL
-     */
-    public boolean isIncludeCacheNameAsParameter()
-    {
-        return includeCacheNameAsParameter;
-    }
-
-    /**
-     * @param includeKeysAndPatternsInURL the includeKeysAndPatternsInURL to set
-     */
-    public void setIncludeKeysAndPatternsAsParameter( boolean includeKeysAndPatternsInURL )
-    {
-        this.includeKeysAndPatternsAsParameter = includeKeysAndPatternsInURL;
-    }
-
-    /**
-     * @return the includeKeysAndPatternsInURL
-     */
-    public boolean isIncludeKeysAndPatternsAsParameter()
-    {
-        return includeKeysAndPatternsAsParameter;
-    }
-
-    /**
-     * @param includeRequestTypeasAsParameter the includeRequestTypeasAsParameter to set
-     */
-    public void setIncludeRequestTypeasAsParameter( boolean includeRequestTypeasAsParameter )
-    {
-        this.includeRequestTypeasAsParameter = includeRequestTypeasAsParameter;
-    }
-
-    /**
-     * @return the includeRequestTypeasAsParameter
-     */
-    public boolean isIncludeRequestTypeasAsParameter()
-    {
-        return includeRequestTypeasAsParameter;
-    }
-
-    /**
-     * @param url the url to set
-     */
-    public void setUrl( String url )
-    {
-        this.url = url;
-    }
-
-    /**
-     * @return the url
-     */
-    public String getUrl()
-    {
-        return url;
-    }
-
-    /**
-     * @param remoteHttpClientClassName the remoteHttpClientClassName to set
-     */
-    public void setRemoteHttpClientClassName( String remoteHttpClientClassName )
-    {
-        this.remoteHttpClientClassName = remoteHttpClientClassName;
-    }
-
-    /**
-     * @return the remoteHttpClientClassName
-     */
-    public String getRemoteHttpClientClassName()
-    {
-        return remoteHttpClientClassName;
-    }
-
-    /**
-     * @return String details
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder buf = new StringBuilder();
-        buf.append( "\n RemoteHttpCacheAttributes" );
-        buf.append( "\n maxConnectionsPerHost = [" + getMaxConnectionsPerHost() + "]" );
-        buf.append( "\n socketTimeoutMillis = [" + getSocketTimeoutMillis() + "]" );
-        buf.append( "\n httpVersion = [" + getHttpVersion() + "]" );
-        buf.append( "\n connectionTimeoutMillis = [" + getConnectionTimeoutMillis() + "]" );
-        buf.append( "\n includeCacheNameAsParameter = [" + isIncludeCacheNameAsParameter() + "]" );
-        buf.append( "\n includeKeysAndPatternsAsParameter = [" + isIncludeKeysAndPatternsAsParameter() + "]" );
-        buf.append( "\n includeRequestTypeasAsParameter = [" + isIncludeRequestTypeasAsParameter() + "]" );
-        buf.append( "\n url = [" + getUrl() + "]" );
-        buf.append( "\n remoteHttpClientClassName = [" + getRemoteHttpClientClassName() + "]" );
-        buf.append( super.toString() );
-        return buf.toString();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/client/RemoteHttpCacheClient.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/client/RemoteHttpCacheClient.java
deleted file mode 100644
index 0f870d4..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/client/RemoteHttpCacheClient.java
+++ /dev/null
@@ -1,484 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.http.client;
-
-import java.io.IOException;
-import java.io.Serializable;
-import java.util.Collections;
-import java.util.Map;
-import java.util.Set;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheDispatcher;
-import org.apache.commons.jcs.auxiliary.remote.http.client.behavior.IRemoteHttpCacheClient;
-import org.apache.commons.jcs.auxiliary.remote.util.RemoteCacheRequestFactory;
-import org.apache.commons.jcs.auxiliary.remote.value.RemoteCacheRequest;
-import org.apache.commons.jcs.auxiliary.remote.value.RemoteCacheResponse;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/** This is the service used by the remote http auxiliary cache. */
-public class RemoteHttpCacheClient<K, V>
-    implements IRemoteHttpCacheClient<K, V>
-{
-    /** The Logger. */
-    private static final Log log = LogManager.getLog( RemoteHttpCacheClient.class );
-
-    /** The internal client. */
-    private IRemoteCacheDispatcher remoteDispatcher;
-
-    /** The remote attributes */
-    private RemoteHttpCacheAttributes remoteHttpCacheAttributes;
-
-    /** Set to true when initialize is called */
-    private boolean initialized = false;
-
-    /** For factory construction. */
-    public RemoteHttpCacheClient()
-    {
-        // does nothing
-    }
-
-    /**
-     * Constructs a client.
-     * <p>
-     * @param attributes
-     */
-    public RemoteHttpCacheClient( RemoteHttpCacheAttributes attributes )
-    {
-        setRemoteHttpCacheAttributes( attributes );
-        initialize( attributes );
-    }
-
-    /**
-     * The provides an extension point. If you want to extend this and use a special dispatcher,
-     * here is the place to do it.
-     * <p>
-     * @param attributes
-     */
-    @Override
-    public void initialize( RemoteHttpCacheAttributes attributes )
-    {
-        setRemoteDispatcher( new RemoteHttpCacheDispatcher( attributes ) );
-
-        log.info( "Created remote Dispatcher. {0}", () -> getRemoteDispatcher() );
-        setInitialized( true );
-    }
-
-    /**
-     * Create a request, process, extract the payload.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @return ICacheElement
-     * @throws IOException
-     */
-    @Override
-    public ICacheElement<K, V> get( String cacheName, K key )
-        throws IOException
-    {
-        return get( cacheName, key, 0 );
-    }
-
-    /**
-     * Create a request, process, extract the payload.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @param requesterId
-     * @return ICacheElement
-     * @throws IOException
-     */
-    @Override
-    public ICacheElement<K, V> get( String cacheName, K key, long requesterId )
-        throws IOException
-    {
-        if ( !isInitialized() )
-        {
-            String message = "The Remote Http Client is not initialized. Cannot process request.";
-            log.warn( message );
-            throw new IOException( message );
-        }
-        RemoteCacheRequest<K, Serializable> remoteHttpCacheRequest =
-            RemoteCacheRequestFactory.createGetRequest( cacheName, key, requesterId );
-
-        RemoteCacheResponse<ICacheElement<K, V>> remoteHttpCacheResponse =
-            getRemoteDispatcher().dispatchRequest( remoteHttpCacheRequest );
-
-        log.debug( "Get [{0}] = {1}", key, remoteHttpCacheResponse );
-
-        if ( remoteHttpCacheResponse != null)
-        {
-            return remoteHttpCacheResponse.getPayload();
-        }
-
-        return null;
-    }
-
-    /**
-     * Gets multiple items from the cache matching the pattern.
-     * <p>
-     * @param cacheName
-     * @param pattern
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache matching the pattern.
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern )
-        throws IOException
-    {
-        return getMatching( cacheName, pattern, 0 );
-    }
-
-    /**
-     * Gets multiple items from the cache matching the pattern.
-     * <p>
-     * @param cacheName
-     * @param pattern
-     * @param requesterId
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache matching the pattern.
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern, long requesterId )
-        throws IOException
-    {
-        if ( !isInitialized() )
-        {
-            String message = "The Remote Http Client is not initialized. Cannot process request.";
-            log.warn( message );
-            throw new IOException( message );
-        }
-
-        RemoteCacheRequest<K, V> remoteHttpCacheRequest =
-            RemoteCacheRequestFactory.createGetMatchingRequest( cacheName, pattern, requesterId );
-
-        RemoteCacheResponse<Map<K, ICacheElement<K, V>>> remoteHttpCacheResponse =
-            getRemoteDispatcher().dispatchRequest( remoteHttpCacheRequest );
-
-        log.debug( "GetMatching [{0}] = {1}", pattern, remoteHttpCacheResponse );
-
-        return remoteHttpCacheResponse.getPayload();
-    }
-
-    /**
-     * Gets multiple items from the cache based on the given set of keys.
-     * <p>
-     * @param cacheName
-     * @param keys
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache for any of these keys
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys )
-        throws IOException
-    {
-        return getMultiple( cacheName, keys, 0 );
-    }
-
-    /**
-     * Gets multiple items from the cache based on the given set of keys.
-     * <p>
-     * @param cacheName
-     * @param keys
-     * @param requesterId
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache for any of these keys
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys, long requesterId )
-        throws IOException
-    {
-        if ( !isInitialized() )
-        {
-            String message = "The Remote Http Client is not initialized.  Cannot process request.";
-            log.warn( message );
-            throw new IOException( message );
-        }
-
-        RemoteCacheRequest<K, V> remoteHttpCacheRequest =
-            RemoteCacheRequestFactory.createGetMultipleRequest( cacheName, keys, requesterId );
-
-        RemoteCacheResponse<Map<K, ICacheElement<K, V>>> remoteHttpCacheResponse =
-            getRemoteDispatcher().dispatchRequest( remoteHttpCacheRequest );
-
-        log.debug( "GetMultiple [{0}] = {1}", keys, remoteHttpCacheResponse );
-
-        return remoteHttpCacheResponse.getPayload();
-    }
-
-    /**
-     * Removes the given key from the specified cache.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @throws IOException
-     */
-    @Override
-    public void remove( String cacheName, K key )
-        throws IOException
-    {
-        remove( cacheName, key, 0 );
-    }
-
-    /**
-     * Removes the given key from the specified cache.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @param requesterId
-     * @throws IOException
-     */
-    @Override
-    public void remove( String cacheName, K key, long requesterId )
-        throws IOException
-    {
-        if ( !isInitialized() )
-        {
-            String message = "The Remote Http Client is not initialized.  Cannot process request.";
-            log.warn( message );
-            throw new IOException( message );
-        }
-
-        RemoteCacheRequest<K, V> remoteHttpCacheRequest =
-            RemoteCacheRequestFactory.createRemoveRequest( cacheName, key, requesterId );
-
-        getRemoteDispatcher().dispatchRequest( remoteHttpCacheRequest );
-    }
-
-    /**
-     * Remove all keys from the specified cache.
-     * <p>
-     * @param cacheName
-     * @throws IOException
-     */
-    @Override
-    public void removeAll( String cacheName )
-        throws IOException
-    {
-        removeAll( cacheName, 0 );
-    }
-
-    /**
-     * Remove all keys from the sepcified cache.
-     * <p>
-     * @param cacheName
-     * @param requesterId
-     * @throws IOException
-     */
-    @Override
-    public void removeAll( String cacheName, long requesterId )
-        throws IOException
-    {
-        if ( !isInitialized() )
-        {
-            String message = "The Remote Http Client is not initialized.  Cannot process request.";
-            log.warn( message );
-            throw new IOException( message );
-        }
-
-        RemoteCacheRequest<K, V> remoteHttpCacheRequest =
-            RemoteCacheRequestFactory.createRemoveAllRequest( cacheName, requesterId );
-
-        getRemoteDispatcher().dispatchRequest( remoteHttpCacheRequest );
-    }
-
-    /**
-     * Puts a cache item to the cache.
-     * <p>
-     * @param item
-     * @throws IOException
-     */
-    @Override
-    public void update( ICacheElement<K, V> item )
-        throws IOException
-    {
-        update( item, 0 );
-    }
-
-    /**
-     * Puts a cache item to the cache.
-     * <p>
-     * @param cacheElement
-     * @param requesterId
-     * @throws IOException
-     */
-    @Override
-    public void update( ICacheElement<K, V> cacheElement, long requesterId )
-        throws IOException
-    {
-        if ( !isInitialized() )
-        {
-            String message = "The Remote Http Client is not initialized.  Cannot process request.";
-            log.warn( message );
-            throw new IOException( message );
-        }
-
-        RemoteCacheRequest<K, V> remoteHttpCacheRequest =
-            RemoteCacheRequestFactory.createUpdateRequest( cacheElement, requesterId );
-
-        getRemoteDispatcher().dispatchRequest( remoteHttpCacheRequest );
-    }
-
-    /**
-     * Frees the specified cache.
-     * <p>
-     * @param cacheName
-     * @throws IOException
-     */
-    @Override
-    public void dispose( String cacheName )
-        throws IOException
-    {
-        if ( !isInitialized() )
-        {
-            String message = "The Remote Http Client is not initialized.  Cannot process request.";
-            log.warn( message );
-            throw new IOException( message );
-        }
-
-        RemoteCacheRequest<K, V> remoteHttpCacheRequest =
-            RemoteCacheRequestFactory.createDisposeRequest( cacheName, 0 );
-
-        getRemoteDispatcher().dispatchRequest( remoteHttpCacheRequest );
-    }
-
-    /**
-     * Frees the specified cache.
-     * <p>
-     * @throws IOException
-     */
-    @Override
-    public void release()
-        throws IOException
-    {
-        // noop
-    }
-
-    /**
-     * Return the keys in this cache.
-     * <p>
-     * @param cacheName the name of the cache
-     * @see org.apache.commons.jcs.auxiliary.AuxiliaryCache#getKeySet()
-     */
-    @Override
-    public Set<K> getKeySet( String cacheName ) throws IOException
-    {
-        if ( !isInitialized() )
-        {
-            String message = "The Remote Http Client is not initialized.  Cannot process request.";
-            log.warn( message );
-            throw new IOException( message );
-        }
-
-        RemoteCacheRequest<String, String> remoteHttpCacheRequest =
-            RemoteCacheRequestFactory.createGetKeySetRequest(cacheName, 0 );
-
-        RemoteCacheResponse<Set<K>> remoteHttpCacheResponse = getRemoteDispatcher().dispatchRequest( remoteHttpCacheRequest );
-
-        if ( remoteHttpCacheResponse != null && remoteHttpCacheResponse.getPayload() != null )
-        {
-            return remoteHttpCacheResponse.getPayload();
-        }
-
-        return Collections.emptySet();
-    }
-
-    /**
-     * Make and alive request.
-     * <p>
-     * @return true if we make a successful alive request.
-     * @throws IOException
-     */
-    @Override
-    public boolean isAlive()
-        throws IOException
-    {
-        if ( !isInitialized() )
-        {
-            String message = "The Remote Http Client is not initialized.  Cannot process request.";
-            log.warn( message );
-            throw new IOException( message );
-        }
-
-        RemoteCacheRequest<K, V> remoteHttpCacheRequest = RemoteCacheRequestFactory.createAliveCheckRequest( 0 );
-        RemoteCacheResponse<String> remoteHttpCacheResponse =
-            getRemoteDispatcher().dispatchRequest( remoteHttpCacheRequest );
-
-        if ( remoteHttpCacheResponse != null )
-        {
-            return remoteHttpCacheResponse.isSuccess();
-        }
-
-        return false;
-    }
-
-    /**
-     * @param remoteDispatcher the remoteDispatcher to set
-     */
-    public void setRemoteDispatcher( IRemoteCacheDispatcher remoteDispatcher )
-    {
-        this.remoteDispatcher = remoteDispatcher;
-    }
-
-    /**
-     * @return the remoteDispatcher
-     */
-    public IRemoteCacheDispatcher getRemoteDispatcher()
-    {
-        return remoteDispatcher;
-    }
-
-    /**
-     * @param remoteHttpCacheAttributes the remoteHttpCacheAttributes to set
-     */
-    public void setRemoteHttpCacheAttributes( RemoteHttpCacheAttributes remoteHttpCacheAttributes )
-    {
-        this.remoteHttpCacheAttributes = remoteHttpCacheAttributes;
-    }
-
-    /**
-     * @return the remoteHttpCacheAttributes
-     */
-    public RemoteHttpCacheAttributes getRemoteHttpCacheAttributes()
-    {
-        return remoteHttpCacheAttributes;
-    }
-
-    /**
-     * @param initialized the initialized to set
-     */
-    protected void setInitialized( boolean initialized )
-    {
-        this.initialized = initialized;
-    }
-
-    /**
-     * @return the initialized
-     */
-    protected boolean isInitialized()
-    {
-        return initialized;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/client/RemoteHttpCacheDispatcher.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/client/RemoteHttpCacheDispatcher.java
deleted file mode 100644
index b6c9faf..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/client/RemoteHttpCacheDispatcher.java
+++ /dev/null
@@ -1,196 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.http.client;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheDispatcher;
-import org.apache.commons.jcs.auxiliary.remote.value.RemoteCacheRequest;
-import org.apache.commons.jcs.auxiliary.remote.value.RemoteCacheResponse;
-import org.apache.commons.jcs.utils.serialization.StandardSerializer;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-import org.apache.http.HttpException;
-import org.apache.http.HttpResponse;
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.client.methods.RequestBuilder;
-import org.apache.http.entity.ByteArrayEntity;
-import org.apache.http.util.EntityUtils;
-
-/** Calls the service. */
-public class RemoteHttpCacheDispatcher
-    extends AbstractHttpClient
-    implements IRemoteCacheDispatcher
-{
-    /** Parameter encoding */
-    private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;
-
-    /** Named of the parameter */
-    private static final String PARAMETER_REQUEST_TYPE = "RequestType";
-
-    /** Named of the parameter */
-    private static final String PARAMETER_KEY = "Key";
-
-    /** Named of the parameter */
-    private static final String PARAMETER_CACHE_NAME = "CacheName";
-
-    /** The Logger. */
-    private static final Log log = LogManager.getLog( RemoteHttpCacheDispatcher.class );
-
-    /** This needs to be standard, since the other side is standard */
-    private StandardSerializer serializer = new StandardSerializer();
-
-    /**
-     * @param remoteHttpCacheAttributes
-     */
-    public RemoteHttpCacheDispatcher( RemoteHttpCacheAttributes remoteHttpCacheAttributes )
-    {
-        super( remoteHttpCacheAttributes );
-    }
-
-    /**
-     * All requests will go through this method.
-     * <p>
-     * TODO consider taking in a URL instead of using the one in the configuration.
-     * <p>
-     * @param remoteCacheRequest
-     * @return RemoteCacheResponse
-     * @throws IOException
-     */
-    @Override
-    public <K, V, T>
-        RemoteCacheResponse<T> dispatchRequest( RemoteCacheRequest<K, V> remoteCacheRequest )
-        throws IOException
-    {
-        try
-        {
-            byte[] requestAsByteArray = serializer.serialize( remoteCacheRequest );
-
-            byte[] responseAsByteArray = processRequest( requestAsByteArray,
-                    remoteCacheRequest,
-                    getRemoteHttpCacheAttributes().getUrl());
-
-            RemoteCacheResponse<T> remoteCacheResponse = null;
-            try
-            {
-                remoteCacheResponse = serializer.deSerialize( responseAsByteArray, null );
-            }
-            catch ( ClassNotFoundException e )
-            {
-                log.error( "Couldn't deserialize the response.", e );
-            }
-            return remoteCacheResponse;
-        }
-        catch ( Exception e )
-        {
-            throw new IOException("Problem dispatching request.", e);
-        }
-    }
-
-    /**
-     * Process single request
-     *
-     * @param requestAsByteArray request body
-     * @param remoteCacheRequest the cache request
-     * @param url target url
-     *
-     * @return byte[] - the response
-     *
-     * @throws IOException
-     * @throws HttpException
-     */
-    protected <K, V> byte[] processRequest( byte[] requestAsByteArray,
-            RemoteCacheRequest<K, V> remoteCacheRequest, String url )
-        throws IOException, HttpException
-    {
-        RequestBuilder builder = RequestBuilder.post( url ).setCharset( DEFAULT_ENCODING );
-
-        if ( getRemoteHttpCacheAttributes().isIncludeCacheNameAsParameter()
-            && remoteCacheRequest.getCacheName() != null )
-        {
-            builder.addParameter( PARAMETER_CACHE_NAME, remoteCacheRequest.getCacheName() );
-        }
-        if ( getRemoteHttpCacheAttributes().isIncludeKeysAndPatternsAsParameter() )
-        {
-            String keyValue = "";
-            switch ( remoteCacheRequest.getRequestType() )
-            {
-                case GET:
-                case REMOVE:
-                case GET_KEYSET:
-                    keyValue = remoteCacheRequest.getKey().toString();
-                    break;
-                case GET_MATCHING:
-                    keyValue = remoteCacheRequest.getPattern();
-                    break;
-                case GET_MULTIPLE:
-                    keyValue = remoteCacheRequest.getKeySet().toString();
-                    break;
-                case UPDATE:
-                    keyValue = remoteCacheRequest.getCacheElement().getKey().toString();
-                    break;
-                default:
-                    break;
-            }
-            builder.addParameter( PARAMETER_KEY, keyValue );
-        }
-        if ( getRemoteHttpCacheAttributes().isIncludeRequestTypeasAsParameter() )
-        {
-            builder.addParameter( PARAMETER_REQUEST_TYPE,
-                remoteCacheRequest.getRequestType().toString() );
-        }
-
-        builder.setEntity(new ByteArrayEntity( requestAsByteArray ));
-        HttpResponse httpResponse = doWebserviceCall( builder );
-        byte[] response = EntityUtils.toByteArray( httpResponse.getEntity() );
-        return response;
-    }
-
-    /**
-     * Called before the execute call on the client.
-     * <p>
-     * @param requestBuilder http method request builder
-     *
-     * @throws IOException
-     */
-    @Override
-    protected void preProcessWebserviceCall( RequestBuilder requestBuilder )
-        throws IOException
-    {
-        // do nothing. Child can override.
-    }
-
-    /**
-     * Called after the execute call on the client.
-     * <p>
-     * @param request http request
-     * @param httpState result of execution
-     *
-     * @throws IOException
-     */
-    @Override
-    protected void postProcessWebserviceCall( HttpUriRequest request, HttpResponse httpState )
-        throws IOException
-    {
-        // do nothing. Child can override.
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/client/RemoteHttpCacheFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/client/RemoteHttpCacheFactory.java
deleted file mode 100644
index 476d176..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/client/RemoteHttpCacheFactory.java
+++ /dev/null
@@ -1,146 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.http.client;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.AbstractAuxiliaryCacheFactory;
-import org.apache.commons.jcs.auxiliary.AuxiliaryCache;
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes;
-import org.apache.commons.jcs.auxiliary.remote.RemoteCacheNoWait;
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheClient;
-import org.apache.commons.jcs.auxiliary.remote.http.client.behavior.IRemoteHttpCacheClient;
-import org.apache.commons.jcs.auxiliary.remote.server.behavior.RemoteType;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-import org.apache.commons.jcs.utils.config.OptionConverter;
-
-/**
- * The RemoteCacheFactory creates remote caches for the cache hub. It returns a no wait facade which
- * is a wrapper around a no wait. The no wait object is either an active connection to a remote
- * cache or a balking zombie if the remote cache is not accessible. It should be transparent to the
- * clients.
- */
-public class RemoteHttpCacheFactory
-    extends AbstractAuxiliaryCacheFactory
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog( RemoteHttpCacheFactory.class );
-
-    /** Monitor thread instance */
-    private RemoteHttpCacheMonitor monitor;
-
-    /**
-     * For LOCAL clients we get a handle to all the failovers, but we do not register a listener
-     * with them. We create the RemoteCacheManager, but we do not get a cache.
-     * <p>
-     * The failover runner will get a cache from the manager. When the primary is restored it will
-     * tell the manager for the failover to deregister the listener.
-     * <p>
-     * @param iaca
-     * @param cacheMgr
-     * @param cacheEventLogger
-     * @param elementSerializer
-     * @return AuxiliaryCache
-     */
-    @Override
-    public <K, V> AuxiliaryCache<K, V> createCache( AuxiliaryCacheAttributes iaca, ICompositeCacheManager cacheMgr,
-                                       ICacheEventLogger cacheEventLogger, IElementSerializer elementSerializer )
-    {
-        RemoteHttpCacheAttributes rca = (RemoteHttpCacheAttributes) iaca;
-
-        // TODO, use the configured value.
-        rca.setRemoteType( RemoteType.LOCAL );
-
-        RemoteHttpClientListener<K, V> listener = new RemoteHttpClientListener<>( rca, cacheMgr, elementSerializer );
-
-        IRemoteHttpCacheClient<K, V> remoteService = createRemoteHttpCacheClientForAttributes(rca);
-
-        IRemoteCacheClient<K, V> remoteCacheClient =
-                new RemoteHttpCache<>( rca, remoteService, listener, monitor );
-        remoteCacheClient.setCacheEventLogger( cacheEventLogger );
-        remoteCacheClient.setElementSerializer( elementSerializer );
-
-        RemoteCacheNoWait<K, V> remoteCacheNoWait = new RemoteCacheNoWait<>( remoteCacheClient );
-        remoteCacheNoWait.setCacheEventLogger( cacheEventLogger );
-        remoteCacheNoWait.setElementSerializer( elementSerializer );
-
-        return remoteCacheNoWait;
-    }
-
-    /**
-     * This is an extension point. The manager and other classes will only create
-     * RemoteHttpCacheClient through this method.
-
-     * @param cattr the cache configuration
-     * @return the client instance
-     */
-    protected <V, K> IRemoteHttpCacheClient<K, V> createRemoteHttpCacheClientForAttributes(RemoteHttpCacheAttributes cattr)
-    {
-        IRemoteHttpCacheClient<K, V> remoteService = OptionConverter.instantiateByClassName( cattr
-                        .getRemoteHttpClientClassName(), null );
-
-        if ( remoteService == null )
-        {
-            log.info( "Creating the default client for {0}",
-                    () -> cattr.getCacheName());
-            remoteService = new RemoteHttpCacheClient<>();
-        }
-
-        remoteService.initialize( cattr );
-        return remoteService;
-    }
-
-    /**
-     * @see org.apache.commons.jcs.auxiliary.AbstractAuxiliaryCacheFactory#initialize()
-     */
-    @Override
-    public void initialize()
-    {
-        super.initialize();
-        monitor = new RemoteHttpCacheMonitor(this);
-        monitor.setDaemon(true);
-        monitor.start();
-    }
-
-    /**
-     * @see org.apache.commons.jcs.auxiliary.AbstractAuxiliaryCacheFactory#dispose()
-     */
-    @Override
-    public void dispose()
-    {
-        if (monitor != null)
-        {
-            monitor.notifyShutdown();
-            try
-            {
-                monitor.join(5000);
-            }
-            catch (InterruptedException e)
-            {
-                // swallow
-            }
-            monitor = null;
-        }
-
-        super.dispose();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/client/RemoteHttpCacheMonitor.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/client/RemoteHttpCacheMonitor.java
deleted file mode 100644
index 6a5a2a7..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/client/RemoteHttpCacheMonitor.java
+++ /dev/null
@@ -1,136 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.http.client;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.io.Serializable;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.apache.commons.jcs.auxiliary.AbstractAuxiliaryCacheMonitor;
-import org.apache.commons.jcs.auxiliary.remote.http.client.behavior.IRemoteHttpCacheClient;
-import org.apache.commons.jcs.engine.CacheStatus;
-
-/**
- * Upon the notification of a connection error, the monitor changes to operate in a time driven
- * mode. That is, it attempts to recover the connections on a periodic basis. When all failed
- * connections are restored, it changes back to the failure driven mode.
- */
-public class RemoteHttpCacheMonitor extends AbstractAuxiliaryCacheMonitor
-{
-    /** Set of remote caches to monitor. This are added on error, if not before. */
-    private final ConcurrentHashMap<RemoteHttpCache<?, ?>, RemoteHttpCache<?, ?>> remoteHttpCaches;
-
-    /** Factory instance */
-    private RemoteHttpCacheFactory factory = null;
-
-    /**
-     * Constructor for the RemoteCacheMonitor object
-     *
-     * @param factory the factory to set
-     */
-    public RemoteHttpCacheMonitor(RemoteHttpCacheFactory factory)
-    {
-        super("JCS-RemoteHttpCacheMonitor");
-        this.factory = factory;
-        this.remoteHttpCaches = new ConcurrentHashMap<>();
-        setIdlePeriod(3000L);
-    }
-
-    /**
-     * Notifies the cache monitor that an error occurred, and kicks off the error recovery process.
-     * <p>
-     * @param remoteCache
-     */
-    public void notifyError( RemoteHttpCache<?, ?> remoteCache )
-    {
-        if ( log.isInfoEnabled() )
-        {
-            log.info( "Notified of an error. " + remoteCache );
-        }
-
-        remoteHttpCaches.put( remoteCache, remoteCache );
-        notifyError();
-    }
-
-    /**
-     * Clean up all resources before shutdown
-     */
-    @Override
-    protected void dispose()
-    {
-        this.remoteHttpCaches.clear();
-    }
-
-    // Avoid the use of any synchronization in the process of monitoring for
-    // performance reasons.
-    // If exception is thrown owing to synchronization,
-    // just skip the monitoring until the next round.
-    /** Main processing method for the RemoteHttpCacheMonitor object */
-    @Override
-    protected void doWork()
-    {
-        // If no factory has been set, skip
-        if (factory == null)
-        {
-            return;
-        }
-
-        // If any cache is in error, it strongly suggests all caches
-        // managed by the same RmicCacheManager instance are in error. So we fix
-        // them once and for all.
-        for (RemoteHttpCache<?, ?> remoteCache : this.remoteHttpCaches.values())
-        {
-            try
-            {
-                if ( remoteCache.getStatus() == CacheStatus.ERROR )
-                {
-                    RemoteHttpCacheAttributes attributes = remoteCache.getRemoteHttpCacheAttributes();
-
-                    IRemoteHttpCacheClient<Serializable, Serializable> remoteService =
-                            factory.createRemoteHttpCacheClientForAttributes( attributes );
-
-                    if ( log.isInfoEnabled() )
-                    {
-                        log.info( "Performing Alive check on service " + remoteService );
-                    }
-                    // If we can't fix them, just skip and re-try in
-                    // the next round.
-                    if ( remoteService.isAlive() )
-                    {
-                        remoteCache.fixCache( remoteService );
-                    }
-                    else
-                    {
-                        allright.set(false);
-                    }
-                    break;
-                }
-            }
-            catch ( IOException ex )
-            {
-                allright.set(false);
-                // Problem encountered in fixing the caches managed by a
-                // RemoteCacheManager instance.
-                // Soldier on to the next RemoteHttpCache.
-                log.error( ex );
-            }
-        }
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/client/RemoteHttpClientListener.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/client/RemoteHttpClientListener.java
deleted file mode 100644
index 4129724..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/client/RemoteHttpClientListener.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.http.client;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.remote.AbstractRemoteCacheListener;
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheAttributes;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-
-/** Does nothing */
-public class RemoteHttpClientListener<K, V>
-    extends AbstractRemoteCacheListener<K, V>
-{
-    /**
-     * Only need one since it does work for all regions, just reference by multiple region names.
-     * <p>
-     * The constructor exports this object, making it available to receive incoming calls. The
-     * callback port is anonymous unless a local port value was specified in the configuration.
-     * <p>
-     * @param irca cache configuration
-     * @param cacheMgr the cache hub
-     * @param elementSerializer a custom serializer
-     */
-    public RemoteHttpClientListener( IRemoteCacheAttributes irca, ICompositeCacheManager cacheMgr, IElementSerializer elementSerializer )
-    {
-        super( irca, cacheMgr, elementSerializer );
-    }
-
-    /** Nothing */
-    @Override
-    public void dispose()
-    {
-        // noop
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/client/behavior/IRemoteHttpCacheClient.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/client/behavior/IRemoteHttpCacheClient.java
deleted file mode 100644
index 9949b7e..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/client/behavior/IRemoteHttpCacheClient.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.http.client.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.remote.http.client.RemoteHttpCacheAttributes;
-import org.apache.commons.jcs.engine.behavior.ICacheServiceNonLocal;
-
-import java.io.IOException;
-
-
-/**
- * It's not entirely clear that this interface is needed. I simply wanted the initialization method.
- * This could be added to the ICacheSerice method.
- */
-public interface IRemoteHttpCacheClient<K, V>
-    extends ICacheServiceNonLocal<K, V>
-{
-    /**
-     * The provides an extension point. If you want to extend this and use a special dispatcher,
-     * here is the place to do it.
-     * <p>
-     * @param attributes
-     */
-    void initialize( RemoteHttpCacheAttributes attributes );
-
-    /**
-     * Make and alive request.
-     * <p>
-     * @return true if we make a successful alive request.
-     * @throws IOException
-     */
-    boolean isAlive()
-        throws IOException;
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/server/AbstractRemoteCacheService.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/server/AbstractRemoteCacheService.java
deleted file mode 100644
index 8a425f4..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/server/AbstractRemoteCacheService.java
+++ /dev/null
@@ -1,601 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.http.server;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.io.Serializable;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheServiceNonLocal;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
-import org.apache.commons.jcs.engine.control.CompositeCache;
-import org.apache.commons.jcs.engine.logging.CacheEvent;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEvent;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * This class contains common methods for remote cache services. Eventually I hope to extract out
- * much of the RMI server to use this as well. I'm starting with the Http service.
- */
-public abstract class AbstractRemoteCacheService<K, V>
-    implements ICacheServiceNonLocal<K, V>
-{
-    /** An optional event logger */
-    private transient ICacheEventLogger cacheEventLogger;
-
-    /** The central hub */
-    private ICompositeCacheManager cacheManager;
-
-    /** Name of the event log source. */
-    private String eventLogSourceName = "AbstractRemoteCacheService";
-
-    /** Number of puts into the cache. */
-    private int puts = 0;
-
-    /** The interval at which we will log updates. */
-    private final int logInterval = 100;
-
-    /** log instance */
-    private static final Log log = LogManager.getLog( AbstractRemoteCacheService.class );
-
-    /**
-     * Creates the super with the needed items.
-     * <p>
-     * @param cacheManager
-     * @param cacheEventLogger
-     */
-    public AbstractRemoteCacheService( ICompositeCacheManager cacheManager, ICacheEventLogger cacheEventLogger )
-    {
-        this.cacheManager = cacheManager;
-        this.cacheEventLogger = cacheEventLogger;
-    }
-
-    /**
-     * @param item
-     * @throws IOException
-     */
-    @Override
-    public void update( ICacheElement<K, V> item )
-        throws IOException
-    {
-        update( item, 0 );
-    }
-
-    /**
-     * The internal processing is wrapped in event logging calls.
-     * <p>
-     * @param item
-     * @param requesterId
-     * @throws IOException
-     */
-    @Override
-    public void update( ICacheElement<K, V> item, long requesterId )
-        throws IOException
-    {
-        ICacheEvent<ICacheElement<K, V>> cacheEvent = createICacheEvent( item, requesterId, ICacheEventLogger.UPDATE_EVENT );
-        try
-        {
-            logUpdateInfo( item );
-
-            processUpdate( item, requesterId );
-        }
-        finally
-        {
-            logICacheEvent( cacheEvent );
-        }
-    }
-
-    /**
-     * The internal processing is wrapped in event logging calls.
-     * <p>
-     * @param item
-     * @param requesterId
-     * @throws IOException
-     */
-    abstract void processUpdate( ICacheElement<K, V> item, long requesterId )
-        throws IOException;
-
-    /**
-     * Log some details.
-     * <p>
-     * @param item
-     */
-    private void logUpdateInfo( ICacheElement<K, V> item )
-    {
-        if ( log.isInfoEnabled() )
-        {
-            // not thread safe, but it doesn't have to be accurate
-            puts++;
-            if ( puts % logInterval == 0 )
-            {
-                log.info( "puts = {0}", puts );
-            }
-        }
-
-        log.debug( "In update, put [{0}] in [{1}]", () -> item.getKey(),
-                () -> item.getCacheName() );
-    }
-
-    /**
-     * Returns a cache value from the specified remote cache; or null if the cache or key does not
-     * exist.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @return ICacheElement
-     * @throws IOException
-     */
-    @Override
-    public ICacheElement<K, V> get( String cacheName, K key )
-        throws IOException
-    {
-        return this.get( cacheName, key, 0 );
-    }
-
-    /**
-     * Returns a cache bean from the specified cache; or null if the key does not exist.
-     * <p>
-     * Adding the requestor id, allows the cache to determine the source of the get.
-     * <p>
-     * The internal processing is wrapped in event logging calls.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @param requesterId
-     * @return ICacheElement
-     * @throws IOException
-     */
-    @Override
-    public ICacheElement<K, V> get( String cacheName, K key, long requesterId )
-        throws IOException
-    {
-        ICacheElement<K, V> element = null;
-        ICacheEvent<K> cacheEvent = createICacheEvent( cacheName, key, requesterId, ICacheEventLogger.GET_EVENT );
-        try
-        {
-            element = processGet( cacheName, key, requesterId );
-        }
-        finally
-        {
-            logICacheEvent( cacheEvent );
-        }
-        return element;
-    }
-
-    /**
-     * Returns a cache bean from the specified cache; or null if the key does not exist.
-     * <p>
-     * Adding the requestor id, allows the cache to determine the source of the get.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @param requesterId
-     * @return ICacheElement
-     * @throws IOException
-     */
-    abstract ICacheElement<K, V> processGet( String cacheName, K key, long requesterId )
-        throws IOException;
-
-    /**
-     * Gets all matching items.
-     * <p>
-     * @param cacheName
-     * @param pattern
-     * @return Map of keys and wrapped objects
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern )
-        throws IOException
-    {
-        return getMatching( cacheName, pattern, 0 );
-    }
-
-    /**
-     * Retrieves all matching keys.
-     * <p>
-     * @param cacheName
-     * @param pattern
-     * @param requesterId
-     * @return Map of keys and wrapped objects
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern, long requesterId )
-        throws IOException
-    {
-        ICacheEvent<String> cacheEvent = createICacheEvent( cacheName, pattern, requesterId,
-                                                    ICacheEventLogger.GETMATCHING_EVENT );
-        try
-        {
-            return processGetMatching( cacheName, pattern, requesterId );
-        }
-        finally
-        {
-            logICacheEvent( cacheEvent );
-        }
-    }
-
-    /**
-     * Retrieves all matching keys.
-     * <p>
-     * @param cacheName
-     * @param pattern
-     * @param requesterId
-     * @return Map of keys and wrapped objects
-     * @throws IOException
-     */
-    abstract Map<K, ICacheElement<K, V>> processGetMatching( String cacheName, String pattern, long requesterId )
-        throws IOException;
-
-    /**
-     * Gets multiple items from the cache based on the given set of keys.
-     * <p>
-     * @param cacheName
-     * @param keys
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache for any of these keys
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys )
-        throws IOException
-    {
-        return this.getMultiple( cacheName, keys, 0 );
-    }
-
-    /**
-     * Gets multiple items from the cache based on the given set of keys.
-     * <p>
-     * The internal processing is wrapped in event logging calls.
-     * <p>
-     * @param cacheName
-     * @param keys
-     * @param requesterId
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache for any of these keys
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys, long requesterId )
-        throws IOException
-    {
-        ICacheEvent<Serializable> cacheEvent = createICacheEvent( cacheName, (Serializable) keys, requesterId,
-                                                    ICacheEventLogger.GETMULTIPLE_EVENT );
-        try
-        {
-            return processGetMultiple( cacheName, keys, requesterId );
-        }
-        finally
-        {
-            logICacheEvent( cacheEvent );
-        }
-    }
-
-    /**
-     * Gets multiple items from the cache based on the given set of keys.
-     * <p>
-     * @param cacheName
-     * @param keys
-     * @param requesterId
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache for any of these keys
-     * @throws IOException
-     */
-    abstract Map<K, ICacheElement<K, V>> processGetMultiple( String cacheName, Set<K> keys, long requesterId )
-        throws IOException;
-
-    /**
-     * Return the keys in this cache.
-     * <p>
-     * @see org.apache.commons.jcs.auxiliary.AuxiliaryCache#getKeySet()
-     */
-    @Override
-    public Set<K> getKeySet( String cacheName )
-    {
-        return processGetKeySet( cacheName );
-    }
-
-    /**
-     * Gets the set of keys of objects currently in the cache.
-     * <p>
-     * @param cacheName
-     * @return Set
-     */
-    public Set<K> processGetKeySet( String cacheName )
-    {
-        CompositeCache<K, V> cache = getCacheManager().getCache( cacheName );
-
-        return cache.getKeySet();
-    }
-
-    /**
-     * Removes the given key from the specified remote cache. Defaults the listener id to 0.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @throws IOException
-     */
-    @Override
-    public void remove( String cacheName, K key )
-        throws IOException
-    {
-        remove( cacheName, key, 0 );
-    }
-
-    /**
-     * Remove the key from the cache region and don't tell the source listener about it.
-     * <p>
-     * The internal processing is wrapped in event logging calls.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @param requesterId
-     * @throws IOException
-     */
-    @Override
-    public void remove( String cacheName, K key, long requesterId )
-        throws IOException
-    {
-        ICacheEvent<K> cacheEvent = createICacheEvent( cacheName, key, requesterId, ICacheEventLogger.REMOVE_EVENT );
-        try
-        {
-            processRemove( cacheName, key, requesterId );
-        }
-        finally
-        {
-            logICacheEvent( cacheEvent );
-        }
-    }
-
-    /**
-     * Remove the key from the cache region and don't tell the source listener about it.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @param requesterId
-     * @throws IOException
-     */
-    abstract void processRemove( String cacheName, K key, long requesterId )
-        throws IOException;
-
-    /**
-     * Remove all keys from the specified remote cache.
-     * <p>
-     * @param cacheName
-     * @throws IOException
-     */
-    @Override
-    public void removeAll( String cacheName )
-        throws IOException
-    {
-        removeAll( cacheName, 0 );
-    }
-
-    /**
-     * Remove all keys from the specified remote cache.
-     * <p>
-     * The internal processing is wrapped in event logging calls.
-     * <p>
-     * @param cacheName
-     * @param requesterId
-     * @throws IOException
-     */
-    @Override
-    public void removeAll( String cacheName, long requesterId )
-        throws IOException
-    {
-        ICacheEvent<String> cacheEvent = createICacheEvent( cacheName, "all", requesterId, ICacheEventLogger.REMOVEALL_EVENT );
-        try
-        {
-            processRemoveAll( cacheName, requesterId );
-        }
-        finally
-        {
-            logICacheEvent( cacheEvent );
-        }
-    }
-
-    /**
-     * Remove all keys from the specified remote cache.
-     * <p>
-     * @param cacheName
-     * @param requesterId
-     * @throws IOException
-     */
-    abstract void processRemoveAll( String cacheName, long requesterId )
-        throws IOException;
-
-    /**
-     * Frees the specified remote cache.
-     * <p>
-     * @param cacheName
-     * @throws IOException
-     */
-    @Override
-    public void dispose( String cacheName )
-        throws IOException
-    {
-        dispose( cacheName, 0 );
-    }
-
-    /**
-     * Frees the specified remote cache.
-     * <p>
-     * @param cacheName
-     * @param requesterId
-     * @throws IOException
-     */
-    public void dispose( String cacheName, long requesterId )
-        throws IOException
-    {
-        ICacheEvent<String> cacheEvent = createICacheEvent( cacheName, "none", requesterId, ICacheEventLogger.DISPOSE_EVENT );
-        try
-        {
-            processDispose( cacheName, requesterId );
-        }
-        finally
-        {
-            logICacheEvent( cacheEvent );
-        }
-    }
-
-    /**
-     * @param cacheName
-     * @param requesterId
-     * @throws IOException
-     */
-    abstract void processDispose( String cacheName, long requesterId )
-        throws IOException;
-
-    /**
-     * Gets the stats attribute of the RemoteCacheServer object.
-     * <p>
-     * @return The stats value
-     * @throws IOException
-     */
-    public String getStats()
-        throws IOException
-    {
-        return cacheManager.getStats();
-    }
-
-    /**
-     * Logs an event if an event logger is configured.
-     * <p>
-     * @param item
-     * @param requesterId
-     * @param eventName
-     * @return ICacheEvent
-     */
-    protected ICacheEvent<ICacheElement<K, V>> createICacheEvent( ICacheElement<K, V> item, long requesterId, String eventName )
-    {
-        if ( cacheEventLogger == null )
-        {
-            return new CacheEvent<>();
-        }
-        String ipAddress = getExtraInfoForRequesterId( requesterId );
-        return cacheEventLogger.createICacheEvent( getEventLogSourceName(), item.getCacheName(), eventName, ipAddress,
-                                                   item );
-    }
-
-    /**
-     * Logs an event if an event logger is configured.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @param requesterId
-     * @param eventName
-     * @return ICacheEvent
-     */
-    protected <T> ICacheEvent<T> createICacheEvent( String cacheName, T key, long requesterId, String eventName )
-    {
-        if ( cacheEventLogger == null )
-        {
-            return new CacheEvent<>();
-        }
-        String ipAddress = getExtraInfoForRequesterId( requesterId );
-        return cacheEventLogger.createICacheEvent( getEventLogSourceName(), cacheName, eventName, ipAddress, key );
-    }
-
-    /**
-     * Logs an event if an event logger is configured.
-     * <p>
-     * @param source
-     * @param eventName
-     * @param optionalDetails
-     */
-    protected void logApplicationEvent( String source, String eventName, String optionalDetails )
-    {
-        if ( cacheEventLogger != null )
-        {
-            cacheEventLogger.logApplicationEvent( source, eventName, optionalDetails );
-        }
-    }
-
-    /**
-     * Logs an event if an event logger is configured.
-     * <p>
-     * @param cacheEvent
-     */
-    protected <T> void logICacheEvent( ICacheEvent<T> cacheEvent )
-    {
-        if ( cacheEventLogger != null )
-        {
-            cacheEventLogger.logICacheEvent( cacheEvent );
-        }
-    }
-
-    /**
-     * Ip address for the client, if one is stored.
-     * <p>
-     * Protected for testing.
-     * <p>
-     * @param requesterId
-     * @return String
-     */
-    protected abstract String getExtraInfoForRequesterId( long requesterId );
-
-    /**
-     * Allows it to be injected.
-     * <p>
-     * @param cacheEventLogger
-     */
-    public void setCacheEventLogger( ICacheEventLogger cacheEventLogger )
-    {
-        this.cacheEventLogger = cacheEventLogger;
-    }
-
-    /**
-     * @param cacheManager the cacheManager to set
-     */
-    protected void setCacheManager( ICompositeCacheManager cacheManager )
-    {
-        this.cacheManager = cacheManager;
-    }
-
-    /**
-     * @return the cacheManager
-     */
-    protected ICompositeCacheManager getCacheManager()
-    {
-        return cacheManager;
-    }
-
-    /**
-     * @param eventLogSourceName the eventLogSourceName to set
-     */
-    protected void setEventLogSourceName( String eventLogSourceName )
-    {
-        this.eventLogSourceName = eventLogSourceName;
-    }
-
-    /**
-     * @return the eventLogSourceName
-     */
-    protected String getEventLogSourceName()
-    {
-        return eventLogSourceName;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/server/RemoteHttpCacheServerAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/server/RemoteHttpCacheServerAttributes.java
deleted file mode 100644
index bbe0257..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/server/RemoteHttpCacheServerAttributes.java
+++ /dev/null
@@ -1,95 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.http.server;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.AbstractAuxiliaryCacheAttributes;
-
-/**
- * Configuration for the RemoteHttpCacheServer. Most of these properties are used only by the
- * service.
- */
-public class RemoteHttpCacheServerAttributes
-    extends AbstractAuxiliaryCacheAttributes
-{
-    /** Don't change. */
-    private static final long serialVersionUID = -3987239306108780496L;
-
-    /** Can a cluster remote put to other remotes */
-    private boolean localClusterConsistency = true;
-
-    /** Can a cluster remote get from other remotes */
-    private boolean allowClusterGet = true;
-
-    /**
-     * Should cluster updates be propagated to the locals
-     * <p>
-     * @return The localClusterConsistency value
-     */
-    public boolean isLocalClusterConsistency()
-    {
-        return localClusterConsistency;
-    }
-
-    /**
-     * Should cluster updates be propagated to the locals
-     * <p>
-     * @param r The new localClusterConsistency value
-     */
-    public void setLocalClusterConsistency( boolean r )
-    {
-        this.localClusterConsistency = r;
-    }
-
-    /**
-     * Should gets from non-cluster clients be allowed to get from other remote auxiliaries.
-     * <p>
-     * @return The localClusterConsistency value
-     */
-    public boolean isAllowClusterGet()
-    {
-        return allowClusterGet;
-    }
-
-    /**
-     * Should we try to get from other cluster servers if we don't find the items locally.
-     * <p>
-     * @param r The new localClusterConsistency value
-     */
-    public void setAllowClusterGet( boolean r )
-    {
-        allowClusterGet = r;
-    }
-
-    /**
-     * @return String details
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder buf = new StringBuilder();
-        buf.append( "\nRemoteHttpCacheServiceAttributes" );
-        buf.append( "\n cacheName = [" + this.getCacheName() + "]" );
-        buf.append( "\n allowClusterGet = [" + this.isAllowClusterGet() + "]" );
-        buf.append( "\n localClusterConsistency = [" + this.isLocalClusterConsistency() + "]" );
-        buf.append( "\n eventQueueType = [" + this.getEventQueueType() + "]" );
-        buf.append( "\n eventQueuePoolName = [" + this.getEventQueuePoolName() + "]" );
-        return buf.toString();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/server/RemoteHttpCacheService.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/server/RemoteHttpCacheService.java
deleted file mode 100644
index f250e91..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/server/RemoteHttpCacheService.java
+++ /dev/null
@@ -1,269 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.http.server;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
-import org.apache.commons.jcs.engine.control.CompositeCache;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-
-/**
- * This does the work. It's called by the processor. The base class wraps the processing calls in
- * event logs, if an event logger is present.
- * <p>
- * For now we assume that all clients are non-cluster clients. And listener notification is not
- * supported.
- */
-public class RemoteHttpCacheService<K, V>
-    extends AbstractRemoteCacheService<K, V>
-{
-    /** The name used in the event logs. */
-    private static final String EVENT_LOG_SOURCE_NAME = "RemoteHttpCacheServer";
-
-    /** The configuration */
-    private final RemoteHttpCacheServerAttributes remoteHttpCacheServerAttributes;
-
-    /**
-     * Create a process with a cache manager.
-     * <p>
-     * @param cacheManager
-     * @param remoteHttpCacheServerAttributes
-     * @param cacheEventLogger
-     */
-    public RemoteHttpCacheService( ICompositeCacheManager cacheManager,
-                                   RemoteHttpCacheServerAttributes remoteHttpCacheServerAttributes,
-                                   ICacheEventLogger cacheEventLogger )
-    {
-        super( cacheManager, cacheEventLogger );
-        setEventLogSourceName( EVENT_LOG_SOURCE_NAME );
-        this.remoteHttpCacheServerAttributes = remoteHttpCacheServerAttributes;
-    }
-
-    /**
-     * Processes a get request.
-     * <p>
-     * If isAllowClusterGet is enabled we will treat this as a normal request or non-remote origins.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @param requesterId
-     * @return ICacheElement
-     * @throws IOException
-     */
-    @Override
-    public ICacheElement<K, V> processGet( String cacheName, K key, long requesterId )
-        throws IOException
-    {
-        CompositeCache<K, V> cache = getCacheManager().getCache( cacheName );
-
-        boolean keepLocal = !remoteHttpCacheServerAttributes.isAllowClusterGet();
-        if ( keepLocal )
-        {
-            return cache.localGet( key );
-        }
-        else
-        {
-            return cache.get( key );
-        }
-    }
-
-    /**
-     * Processes a get request.
-     * <p>
-     * If isAllowClusterGet is enabled we will treat this as a normal request of non-remote
-     * origination.
-     * <p>
-     * @param cacheName
-     * @param keys
-     * @param requesterId
-     * @return Map
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> processGetMultiple( String cacheName, Set<K> keys, long requesterId )
-        throws IOException
-    {
-        CompositeCache<K, V> cache = getCacheManager().getCache( cacheName );
-
-        boolean keepLocal = !remoteHttpCacheServerAttributes.isAllowClusterGet();
-        if ( keepLocal )
-        {
-            return cache.localGetMultiple( keys );
-        }
-        else
-        {
-            return cache.getMultiple( keys );
-        }
-    }
-
-    /**
-     * Processes a get request.
-     * <p>
-     * If isAllowClusterGet is enabled we will treat this as a normal request of non-remote
-     * origination.
-     * <p>
-     * @param cacheName
-     * @param pattern
-     * @param requesterId
-     * @return Map
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> processGetMatching( String cacheName, String pattern, long requesterId )
-        throws IOException
-    {
-        CompositeCache<K, V> cache = getCacheManager().getCache( cacheName );
-
-        boolean keepLocal = !remoteHttpCacheServerAttributes.isAllowClusterGet();
-        if ( keepLocal )
-        {
-            return cache.localGetMatching( pattern );
-        }
-        else
-        {
-            return cache.getMatching( pattern );
-        }
-    }
-
-    /**
-     * Processes an update request.
-     * <p>
-     * If isLocalClusterConsistency is enabled we will treat this as a normal request of non-remote
-     * origination.
-     * <p>
-     * @param item
-     * @param requesterId
-     * @throws IOException
-     */
-    @Override
-    public void processUpdate( ICacheElement<K, V> item, long requesterId )
-        throws IOException
-    {
-        CompositeCache<K, V> cache = getCacheManager().getCache( item.getCacheName() );
-
-        boolean keepLocal = !remoteHttpCacheServerAttributes.isLocalClusterConsistency();
-        if ( keepLocal )
-        {
-            cache.localUpdate( item );
-        }
-        else
-        {
-            cache.update( item );
-        }
-    }
-
-    /**
-     * Processes a remove request.
-     * <p>
-     * If isLocalClusterConsistency is enabled we will treat this as a normal request of non-remote
-     * origination.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @param requesterId
-     * @throws IOException
-     */
-    @Override
-    public void processRemove( String cacheName, K key, long requesterId )
-        throws IOException
-    {
-        CompositeCache<K, V> cache = getCacheManager().getCache( cacheName );
-
-        boolean keepLocal = !remoteHttpCacheServerAttributes.isLocalClusterConsistency();
-        if ( keepLocal )
-        {
-            cache.localRemove( key );
-        }
-        else
-        {
-            cache.remove( key );
-        }
-    }
-
-    /**
-     * Processes a removeAll request.
-     * <p>
-     * If isLocalClusterConsistency is enabled we will treat this as a normal request of non-remote
-     * origination.
-     * <p>
-     * @param cacheName
-     * @param requesterId
-     * @throws IOException
-     */
-    @Override
-    public void processRemoveAll( String cacheName, long requesterId )
-        throws IOException
-    {
-        CompositeCache<K, V> cache = getCacheManager().getCache( cacheName );
-
-        boolean keepLocal = !remoteHttpCacheServerAttributes.isLocalClusterConsistency();
-        if ( keepLocal )
-        {
-            cache.localRemoveAll();
-        }
-        else
-        {
-            cache.removeAll();
-        }
-    }
-
-    /**
-     * Processes a shutdown request.
-     * <p>
-     * @param cacheName
-     * @param requesterId
-     * @throws IOException
-     */
-    @Override
-    public void processDispose( String cacheName, long requesterId )
-        throws IOException
-    {
-        CompositeCache<K, V> cache = getCacheManager().getCache( cacheName );
-        cache.dispose();
-    }
-
-    /**
-     * This general method should be deprecated.
-     * <p>
-     * @throws IOException
-     */
-    @Override
-    public void release()
-        throws IOException
-    {
-        //nothing.
-    }
-
-    /**
-     * This is called by the event log.
-     * <p>
-     * @param requesterId
-     * @return requesterId + ""
-     */
-    @Override
-    protected String getExtraInfoForRequesterId( long requesterId )
-    {
-        return requesterId + "";
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/server/RemoteHttpCacheServlet.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/server/RemoteHttpCacheServlet.java
deleted file mode 100644
index 50373f1..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/http/server/RemoteHttpCacheServlet.java
+++ /dev/null
@@ -1,380 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.http.server;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.ObjectInputStream;
-import java.io.OutputStream;
-import java.io.Serializable;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
-
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.apache.commons.jcs.access.exception.CacheException;
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheConfigurator;
-import org.apache.commons.jcs.auxiliary.remote.http.behavior.IRemoteHttpCacheConstants;
-import org.apache.commons.jcs.auxiliary.remote.value.RemoteCacheRequest;
-import org.apache.commons.jcs.auxiliary.remote.value.RemoteCacheResponse;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheServiceNonLocal;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
-import org.apache.commons.jcs.engine.control.CompositeCacheManager;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-import org.apache.commons.jcs.io.ObjectInputStreamClassLoaderAware;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-import org.apache.commons.jcs.utils.config.PropertySetter;
-import org.apache.commons.jcs.utils.serialization.StandardSerializer;
-
-/**
- * This servlet simply reads and writes objects. The requests are packaged in a general wrapper. The
- * processor works on the wrapper object and returns a response wrapper.
- */
-public class RemoteHttpCacheServlet
-    extends HttpServlet
-{
-    /** Don't change. */
-    private static final long serialVersionUID = 8752849397531933346L;
-
-    /** The Logger. */
-    private static final Log log = LogManager.getLog( RemoteHttpCacheServlet.class );
-
-    /** The cache manager */
-    private static CompositeCacheManager cacheMgr;
-
-    /** The service that does the work. */
-    private static ICacheServiceNonLocal<Serializable, Serializable> remoteCacheService;
-
-    /** This needs to be standard, since the other side is standard */
-    private final StandardSerializer serializer = new StandardSerializer();
-
-    /** Number of service calls. */
-    private int serviceCalls = 0;
-
-    /** The interval at which we will log the count. */
-    private final int logInterval = 100;
-
-    /**
-     * Initializes the cache.
-     * <p>
-     * This provides an easy extension point. Simply extend this servlet and override the init
-     * method to change the way the properties are loaded.
-     * @param config
-     * @throws ServletException
-     */
-    @Override
-    public void init( ServletConfig config )
-        throws ServletException
-    {
-        try
-        {
-            cacheMgr = CompositeCacheManager.getInstance();
-        }
-        catch (CacheException e)
-        {
-            throw new ServletException(e);
-        }
-
-        remoteCacheService = createRemoteHttpCacheService( cacheMgr );
-
-        super.init( config );
-    }
-
-    /**
-     * Read the request, call the processor, write the response.
-     * <p>
-     * @param request
-     * @param response
-     * @throws ServletException
-     * @throws IOException
-     */
-    @Override
-    public void service( HttpServletRequest request, HttpServletResponse response )
-        throws ServletException, IOException
-    {
-        incrementServiceCallCount();
-        log.debug( "Servicing a request. {0}", request );
-
-        RemoteCacheRequest<Serializable, Serializable> remoteRequest = readRequest( request );
-        RemoteCacheResponse<Object> cacheResponse = processRequest( remoteRequest );
-
-        writeResponse( response, cacheResponse );
-    }
-
-    /**
-     * Read the request from the input stream.
-     * <p>
-     * @param request
-     * @return RemoteHttpCacheRequest
-     */
-    protected RemoteCacheRequest<Serializable, Serializable> readRequest( HttpServletRequest request )
-    {
-        RemoteCacheRequest<Serializable, Serializable> remoteRequest = null;
-        try
-        {
-            InputStream inputStream = request.getInputStream();
-            log.debug( "After getting input stream and before reading it" );
-
-            remoteRequest = readRequestFromStream( inputStream );
-        }
-        catch ( Exception e )
-        {
-            log.error( "Could not get a RemoteHttpCacheRequest object from the input stream.", e );
-        }
-        return remoteRequest;
-    }
-
-    /**
-     * Reads the response from the stream and then closes it.
-     * <p>
-     * @param inputStream
-     * @return RemoteHttpCacheRequest
-     * @throws IOException
-     * @throws ClassNotFoundException
-     */
-    protected RemoteCacheRequest<Serializable, Serializable> readRequestFromStream( InputStream inputStream )
-        throws IOException, ClassNotFoundException
-    {
-        ObjectInputStream ois = new ObjectInputStreamClassLoaderAware( inputStream, null );
-
-        @SuppressWarnings("unchecked") // Need to cast from Object
-        RemoteCacheRequest<Serializable, Serializable> remoteRequest
-            = (RemoteCacheRequest<Serializable, Serializable>) ois.readObject();
-        ois.close();
-        return remoteRequest;
-    }
-
-    /**
-     * Write the response to the output stream.
-     * <p>
-     * @param response
-     * @param cacheResponse
-     */
-    protected void writeResponse( HttpServletResponse response, RemoteCacheResponse<Object> cacheResponse )
-    {
-        try
-        {
-            response.setContentType( "application/octet-stream" );
-
-            byte[] responseAsByteAray = serializer.serialize( cacheResponse );
-            response.setContentLength( responseAsByteAray.length );
-
-            OutputStream outputStream = response.getOutputStream();
-            log.debug( "Opened output stream.  Response size: {0}",
-                    () -> responseAsByteAray.length );
-            // WRITE
-            outputStream.write( responseAsByteAray );
-            outputStream.flush();
-            outputStream.close();
-        }
-        catch ( IOException e )
-        {
-            log.error( "Problem writing response. {0}", cacheResponse, e );
-        }
-    }
-
-    /**
-     * Processes the request. It will call the appropriate method on the service
-     * <p>
-     * @param request
-     * @return RemoteHttpCacheResponse, never null
-     */
-    protected RemoteCacheResponse<Object> processRequest( RemoteCacheRequest<Serializable, Serializable> request )
-    {
-        RemoteCacheResponse<Object> response = new RemoteCacheResponse<>();
-
-        if ( request == null )
-        {
-            String message = "The request is null. Cannot process";
-            log.warn( message );
-            response.setSuccess( false );
-            response.setErrorMessage( message );
-        }
-        else
-        {
-            try
-            {
-                switch ( request.getRequestType() )
-                {
-                    case GET:
-                        ICacheElement<Serializable, Serializable> element =
-                            remoteCacheService.get( request.getCacheName(), request.getKey(), request.getRequesterId() );
-                        response.setPayload(element);
-                        break;
-                    case GET_MULTIPLE:
-                        Map<Serializable, ICacheElement<Serializable, Serializable>> elementMap =
-                            remoteCacheService.getMultiple( request.getCacheName(), request.getKeySet(), request.getRequesterId() );
-                        if ( elementMap != null )
-                        {
-                            Map<Serializable, ICacheElement<Serializable, Serializable>> map = new HashMap<>();
-                            map.putAll(elementMap);
-                            response.setPayload(map);
-                        }
-                        break;
-                    case GET_MATCHING:
-                        Map<Serializable, ICacheElement<Serializable, Serializable>> elementMapMatching =
-                            remoteCacheService.getMatching( request.getCacheName(), request.getPattern(), request.getRequesterId() );
-                        if ( elementMapMatching != null )
-                        {
-                            Map<Serializable, ICacheElement<Serializable, Serializable>> map = new HashMap<>();
-                            map.putAll(elementMapMatching);
-                            response.setPayload(map);
-                        }
-                        break;
-                    case REMOVE:
-                        remoteCacheService.remove( request.getCacheName(), request.getKey(), request.getRequesterId() );
-                        break;
-                    case REMOVE_ALL:
-                        remoteCacheService.removeAll( request.getCacheName(), request.getRequesterId() );
-                        break;
-                    case UPDATE:
-                        remoteCacheService.update( request.getCacheElement(), request.getRequesterId() );
-                        break;
-                    case ALIVE_CHECK:
-                    case DISPOSE:
-                        response.setSuccess( true );
-                        // DO NOTHING
-                        break;
-                    case GET_KEYSET:
-                        Set<Serializable> keys = remoteCacheService.getKeySet( request.getCacheName() );
-                        response.setPayload( keys );
-                        break;
-                    default:
-                        String message = "Unknown event type.  Cannot process " + request;
-                        log.warn( message );
-                        response.setSuccess( false );
-                        response.setErrorMessage( message );
-                        break;
-                }
-            }
-            catch ( IOException e )
-            {
-                String message = "Problem processing request. " + request + " Error: " + e.getMessage();
-                log.error( message, e );
-                response.setSuccess( false );
-                response.setErrorMessage( message );
-            }
-        }
-
-        return response;
-    }
-
-    /**
-     * Configures the attributes and the event logger and constructs a service.
-     * <p>
-     * @param cacheManager
-     * @return RemoteHttpCacheService
-     */
-    protected <K, V> RemoteHttpCacheService<K, V> createRemoteHttpCacheService( ICompositeCacheManager cacheManager )
-    {
-        Properties props = cacheManager.getConfigurationProperties();
-        ICacheEventLogger cacheEventLogger = configureCacheEventLogger( props );
-        RemoteHttpCacheServerAttributes attributes = configureRemoteHttpCacheServerAttributes( props );
-
-        RemoteHttpCacheService<K, V> service = new RemoteHttpCacheService<>( cacheManager, attributes, cacheEventLogger );
-        log.info( "Created new RemoteHttpCacheService {0}", service );
-        return service;
-    }
-
-    /**
-     * Tries to get the event logger.
-     * <p>
-     * @param props
-     * @return ICacheEventLogger
-     */
-    protected ICacheEventLogger configureCacheEventLogger( Properties props )
-    {
-        ICacheEventLogger cacheEventLogger = AuxiliaryCacheConfigurator
-            .parseCacheEventLogger( props, IRemoteHttpCacheConstants.HTTP_CACHE_SERVER_PREFIX );
-
-        return cacheEventLogger;
-    }
-
-    /**
-     * Configure.
-     * <p>
-     * jcs.remotehttpcache.serverattributes.ATTRIBUTENAME=ATTRIBUTEVALUE
-     * <p>
-     * @param prop
-     * @return RemoteCacheServerAttributesconfigureRemoteCacheServerAttributes
-     */
-    protected RemoteHttpCacheServerAttributes configureRemoteHttpCacheServerAttributes( Properties prop )
-    {
-        RemoteHttpCacheServerAttributes rcsa = new RemoteHttpCacheServerAttributes();
-
-        // configure automatically
-        PropertySetter.setProperties( rcsa, prop,
-                                      IRemoteHttpCacheConstants.HTTP_CACHE_SERVER_ATTRIBUTES_PROPERTY_PREFIX + "." );
-
-        return rcsa;
-    }
-
-    /**
-     * @param rcs the remoteCacheService to set
-     */
-    protected void setRemoteCacheService(ICacheServiceNonLocal<Serializable, Serializable> rcs)
-    {
-        remoteCacheService = rcs;
-    }
-
-    /**
-     * Log some details.
-     */
-    private void incrementServiceCallCount()
-    {
-        // not thread safe, but it doesn't have to be accurate
-        serviceCalls++;
-        if ( log.isInfoEnabled() )
-        {
-            if ( serviceCalls % logInterval == 0 )
-            {
-                log.info( "serviceCalls = {0}", serviceCalls );
-            }
-        }
-    }
-
-    /** Release the cache manager. */
-    @Override
-    public void destroy()
-    {
-        log.info( "Servlet Destroyed, shutting down JCS." );
-
-        cacheMgr.shutDown();
-    }
-
-    /**
-     * Get servlet information
-     * <p>
-     * @return basic info
-     */
-    @Override
-    public String getServletInfo()
-    {
-        return "RemoteHttpCacheServlet";
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/server/RegistryKeepAliveRunner.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/server/RegistryKeepAliveRunner.java
deleted file mode 100644
index b09c3ab..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/server/RegistryKeepAliveRunner.java
+++ /dev/null
@@ -1,196 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.server;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.rmi.Naming;
-import java.rmi.Remote;
-import java.rmi.RemoteException;
-import java.rmi.registry.Registry;
-
-import org.apache.commons.jcs.auxiliary.remote.RemoteUtils;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * This class tries to keep the registry alive. If if is able to create a registry, it will also
- * rebind the remote cache server.
- */
-public class RegistryKeepAliveRunner
-    implements Runnable
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog( RegistryKeepAliveRunner.class );
-
-    /** The URL of the service to look for. */
-    private String namingURL;
-
-    /** The service name. */
-    private String serviceName;
-
-    /** the port on which to start the registry */
-    private int registryPort;
-
-    /** An optional event logger */
-    private ICacheEventLogger cacheEventLogger;
-
-    /** the registry */
-    private Registry registry;
-
-    /**
-     * @param registryHost - Hostname of the registry
-     * @param registryPort - the port on which to start the registry
-     * @param serviceName
-     */
-    public RegistryKeepAliveRunner( String registryHost, int registryPort, String serviceName )
-    {
-        this.namingURL = RemoteUtils.getNamingURL(registryHost, registryPort, serviceName);
-        this.serviceName = serviceName;
-        this.registryPort = registryPort;
-    }
-
-    /**
-     * Tries to lookup the server. If unsuccessful it will rebind the server using the factory
-     * rebind method.
-     * <p>
-     */
-    @Override
-    public void run()
-    {
-        checkAndRestoreIfNeeded();
-    }
-
-    /**
-     * Tries to lookup the server. If unsuccessful it will rebind the server using the factory
-     * rebind method.
-     */
-    protected void checkAndRestoreIfNeeded()
-    {
-        log.debug( "looking up server {0}", namingURL );
-
-        try
-        {
-            Object obj = Naming.lookup( namingURL );
-
-            // Successful connection to the remote server.
-            String message = "RMI registry looks fine.  Found [" + obj + "] in registry [" + namingURL + "]";
-            if ( cacheEventLogger != null )
-            {
-                cacheEventLogger.logApplicationEvent( "RegistryKeepAliveRunner", "Naming.lookup", message );
-            }
-            log.debug( message );
-        }
-        catch ( Exception ex )
-        {
-            // Failed to connect to the remote server.
-            String message = "Problem finding server at [" + namingURL
-                + "].  Will attempt to start registry and rebind.";
-            log.error( message, ex );
-            if ( cacheEventLogger != null )
-            {
-                cacheEventLogger.logError( "RegistryKeepAliveRunner", "Naming.lookup", message + ":" + ex.getMessage() );
-            }
-            createAndRegister( serviceName );
-        }
-    }
-
-    /**
-     * Creates the registry and registers the server.
-     * <p>
-     * @param serviceName the service name
-     */
-    protected void createAndRegister( String serviceName )
-    {
-        createReqistry( serviceName );
-        registerServer( serviceName );
-    }
-
-    /**
-     * Try to create the registry. Log errors
-     * <p>
-     * @param serviceName the service name
-     */
-    protected void createReqistry( String serviceName )
-    {
-        // TODO: Refactor method signature. This is ugly but required to keep the binary API compatibility
-        this.registry = RemoteUtils.createRegistry(registryPort);
-
-        if ( cacheEventLogger != null )
-        {
-            if (this.registry != null)
-            {
-                cacheEventLogger.logApplicationEvent( "RegistryKeepAliveRunner", "createRegistry",
-                        "Successfully created registry [" + serviceName + "]." );
-            }
-            else
-            {
-                cacheEventLogger.logError( "RegistryKeepAliveRunner", "createRegistry",
-                        "Could not start registry [" + serviceName + "]." );
-            }
-        }
-    }
-
-    /**
-     * Try to rebind the server.
-     * <p>
-     * @param serviceName the service name
-     */
-    protected void registerServer( String serviceName )
-    {
-        try
-        {
-            // try to rebind anyway
-            Remote server = RemoteCacheServerFactory.getRemoteCacheServer();
-
-            if ( server == null )
-            {
-                throw new RemoteException( "Cannot register the server until it is created." );
-            }
-
-            this.registry.rebind( serviceName, server );
-            String message = "Successfully rebound server to registry [" + serviceName + "].";
-            if ( cacheEventLogger != null )
-            {
-                cacheEventLogger.logApplicationEvent( "RegistryKeepAliveRunner", "registerServer", message );
-            }
-            log.info( message );
-        }
-        catch ( RemoteException e )
-        {
-            String message = "Could not rebind server to registry [" + serviceName + "].";
-            log.error( message, e );
-            if ( cacheEventLogger != null )
-            {
-                cacheEventLogger.logError( "RegistryKeepAliveRunner", "registerServer", message + ":"
-                    + e.getMessage() );
-            }
-        }
-    }
-
-    /**
-     * Allows it to be injected.
-     * <p>
-     * @param cacheEventLogger
-     */
-    public void setCacheEventLogger( ICacheEventLogger cacheEventLogger )
-    {
-        this.cacheEventLogger = cacheEventLogger;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/server/RemoteCacheServer.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/server/RemoteCacheServer.java
deleted file mode 100644
index 1d91bb6..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/server/RemoteCacheServer.java
+++ /dev/null
@@ -1,1528 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.server;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.io.Serializable;
-import java.rmi.RemoteException;
-import java.rmi.registry.Registry;
-import java.rmi.server.RMISocketFactory;
-import java.rmi.server.UnicastRemoteObject;
-import java.rmi.server.Unreferenced;
-import java.util.Collections;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-import org.apache.commons.jcs.access.exception.CacheException;
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheListener;
-import org.apache.commons.jcs.auxiliary.remote.server.behavior.IRemoteCacheServer;
-import org.apache.commons.jcs.auxiliary.remote.server.behavior.IRemoteCacheServerAttributes;
-import org.apache.commons.jcs.auxiliary.remote.server.behavior.RemoteType;
-import org.apache.commons.jcs.engine.CacheEventQueueFactory;
-import org.apache.commons.jcs.engine.CacheListeners;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheEventQueue;
-import org.apache.commons.jcs.engine.behavior.ICacheListener;
-import org.apache.commons.jcs.engine.control.CompositeCache;
-import org.apache.commons.jcs.engine.control.CompositeCacheManager;
-import org.apache.commons.jcs.engine.logging.CacheEvent;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEvent;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-import org.apache.commons.jcs.utils.timing.ElapsedTimer;
-
-/**
- * This class provides remote cache services. The remote cache server propagates events from local
- * caches to other local caches. It can also store cached data, making it available to new clients.
- * <p>
- * Remote cache servers can be clustered. If the cache used by this remote cache is configured to
- * use a remote cache of type cluster, the two remote caches will communicate with each other.
- * Remote and put requests can be sent from one remote to another. If they are configured to
- * broadcast such event to their client, then remove an puts can be sent to all locals in the
- * cluster.
- * <p>
- * Get requests are made between clustered servers if AllowClusterGet is true. You can setup several
- * clients to use one remote server and several to use another. The get local will be distributed
- * between the two servers. Since caches are usually high get and low put, this should allow you to
- * scale.
- */
-public class RemoteCacheServer<K, V>
-    extends UnicastRemoteObject
-    implements IRemoteCacheServer<K, V>, Unreferenced
-{
-    public static final String DFEAULT_REMOTE_CONFIGURATION_FILE = "/remote.cache.ccf";
-
-    /** For serialization. Don't change. */
-    private static final long serialVersionUID = -8072345435941473116L;
-
-    /** log instance */
-    private static final Log log = LogManager.getLog( RemoteCacheServer.class );
-
-    /** Number of puts into the cache. */
-    private int puts = 0;
-
-    /** Maps cache name to CacheListeners object. association of listeners (regions). */
-    private final transient ConcurrentMap<String, CacheListeners<K, V>> cacheListenersMap =
-        new ConcurrentHashMap<>();
-
-    /** maps cluster listeners to regions. */
-    private final transient ConcurrentMap<String, CacheListeners<K, V>> clusterListenersMap =
-        new ConcurrentHashMap<>();
-
-    /** The central hub */
-    private transient CompositeCacheManager cacheManager;
-
-    /** relates listener id with a type */
-    private final ConcurrentMap<Long, RemoteType> idTypeMap = new ConcurrentHashMap<>();
-
-    /** relates listener id with an ip address */
-    private final ConcurrentMap<Long, String> idIPMap = new ConcurrentHashMap<>();
-
-    /** Used to get the next listener id. */
-    private final int[] listenerId = new int[1];
-
-    /** Configuration settings. */
-    // package protected for access by unit test code
-    final IRemoteCacheServerAttributes remoteCacheServerAttributes;
-
-    /** The interval at which we will log updates. */
-    private final int logInterval = 100;
-
-    /** An optional event logger */
-    private transient ICacheEventLogger cacheEventLogger;
-
-    /**
-     * Constructor for the RemoteCacheServer object. This initializes the server with the values
-     * from the properties object.
-     * <p>
-     * @param rcsa
-     * @param config cache hub configuration
-     * @throws RemoteException
-     */
-    protected RemoteCacheServer( IRemoteCacheServerAttributes rcsa, Properties config )
-        throws RemoteException
-    {
-        super( rcsa.getServicePort() );
-        this.remoteCacheServerAttributes = rcsa;
-        init( config );
-    }
-
-    /**
-     * Constructor for the RemoteCacheServer object. This initializes the server with the values
-     * from the properties object.
-     * <p>
-     * @param rcsa
-     * @param config cache hub configuration
-     * @param customRMISocketFactory
-     * @throws RemoteException
-     */
-    protected RemoteCacheServer( IRemoteCacheServerAttributes rcsa, Properties config, RMISocketFactory customRMISocketFactory )
-        throws RemoteException
-    {
-        super( rcsa.getServicePort(), customRMISocketFactory, customRMISocketFactory );
-        this.remoteCacheServerAttributes = rcsa;
-        init( config );
-    }
-
-    /**
-     * Initialize the RMI Cache Server from a properties object.
-     * <p>
-     * @param prop the configuration properties
-     * @throws RemoteException if the configuration of the cache manager instance fails
-     */
-    private void init( Properties prop ) throws RemoteException
-    {
-        try
-        {
-            cacheManager = createCacheManager( prop );
-        }
-        catch (CacheException e)
-        {
-            throw new RemoteException(e.getMessage(), e);
-        }
-
-        // cacheManager would have created a number of ICache objects.
-        // Use these objects to set up the cacheListenersMap.
-        cacheManager.getCacheNames().forEach(name -> {
-            CompositeCache<K, V> cache = cacheManager.getCache( name );
-            cacheListenersMap.put( name, new CacheListeners<>( cache ) );
-        });
-    }
-
-    /**
-     * Subclass can override this method to create the specific cache manager.
-     * <p>
-     * @param prop the configuration object.
-     * @return The cache hub configured with this configuration.
-     *
-     * @throws CacheException if the configuration cannot be loaded
-     */
-    private CompositeCacheManager createCacheManager( Properties prop ) throws CacheException
-    {
-        CompositeCacheManager hub = CompositeCacheManager.getUnconfiguredInstance();
-        hub.configure( prop );
-        return hub;
-    }
-
-    /**
-     * Puts a cache bean to the remote cache and notifies all listeners which <br>
-     * <ol>
-     * <li>have a different listener id than the originating host;</li>
-     * <li>are currently subscribed to the related cache.</li>
-     * </ol>
-     * <p>
-     * @param item
-     * @throws IOException
-     */
-    public void put( ICacheElement<K, V> item )
-        throws IOException
-    {
-        update( item );
-    }
-
-    /**
-     * @param item
-     * @throws IOException
-     */
-    @Override
-    public void update( ICacheElement<K, V> item )
-        throws IOException
-    {
-        update( item, 0 );
-    }
-
-    /**
-     * The internal processing is wrapped in event logging calls.
-     * <p>
-     * @param item
-     * @param requesterId
-     * @throws IOException
-     */
-    @Override
-    public void update( ICacheElement<K, V> item, long requesterId )
-        throws IOException
-    {
-        ICacheEvent<ICacheElement<K, V>> cacheEvent = createICacheEvent( item, requesterId, ICacheEventLogger.UPDATE_EVENT );
-        try
-        {
-            processUpdate( item, requesterId );
-        }
-        finally
-        {
-            logICacheEvent( cacheEvent );
-        }
-    }
-
-    /**
-     * An update can come from either a local cache's remote auxiliary, or it can come from a remote
-     * server. A remote server is considered a a source of type cluster.
-     * <p>
-     * If the update came from a cluster, then we should tell the cache manager that this was a
-     * remote put. This way, any lateral and remote auxiliaries configured for the region will not
-     * be updated. This is basically how a remote listener works when plugged into a local cache.
-     * <p>
-     * If the cluster is configured to keep local cluster consistency, then all listeners will be
-     * updated. This allows cluster server A to update cluster server B and then B to update its
-     * clients if it is told to keep local cluster consistency. Otherwise, server A will update
-     * server B and B will not tell its clients. If you cluster using lateral caches for instance,
-     * this is how it will work. Updates to a cluster node, will never get to the leaves. The remote
-     * cluster, with local cluster consistency, allows you to update leaves. This basically allows
-     * you to have a failover remote server.
-     * <p>
-     * Since currently a cluster will not try to get from other cluster servers, you can scale a bit
-     * with a cluster configuration. Puts and removes will be broadcasted to all clients, but the
-     * get load on a remote server can be reduced.
-     * <p>
-     * @param item
-     * @param requesterId
-     */
-    private void processUpdate( ICacheElement<K, V> item, long requesterId )
-    {
-        ElapsedTimer timer = new ElapsedTimer();
-        logUpdateInfo( item );
-
-        try
-        {
-            CacheListeners<K, V> cacheDesc = getCacheListeners( item.getCacheName() );
-            /* Object val = */item.getVal();
-
-            boolean fromCluster = isRequestFromCluster( requesterId );
-
-            log.debug( "In update, requesterId = [{0}] fromCluster = {1}", requesterId, fromCluster );
-
-            // ordered cache item update and notification.
-            synchronized ( cacheDesc )
-            {
-                try
-                {
-                    CompositeCache<K, V> c = (CompositeCache<K, V>) cacheDesc.cache;
-
-                    // If the source of this request was not from a cluster,
-                    // then consider it a local update. The cache manager will
-                    // try to
-                    // update all auxiliaries.
-                    //
-                    // This requires that two local caches not be connected to
-                    // two clustered remote caches. The failover runner will
-                    // have to make sure of this. ALos, the local cache needs
-                    // avoid updating this source. Will need to pass the source
-                    // id somehow. The remote cache should update all local
-                    // caches
-                    // but not update the cluster source. Cluster remote caches
-                    // should only be updated by the server and not the
-                    // RemoteCache.
-                    if ( fromCluster )
-                    {
-                        log.debug( "Put FROM cluster, NOT updating other auxiliaries for region. "
-                                + " requesterId [{0}]", requesterId );
-                        c.localUpdate( item );
-                    }
-                    else
-                    {
-                        log.debug( "Put NOT from cluster, updating other auxiliaries for region. "
-                                + " requesterId [{0}]", requesterId );
-                        c.update( item );
-                    }
-                }
-                catch ( IOException ce )
-                {
-                    // swallow
-                    log.info( "Exception caught updating item. requesterId [{0}]: {1}",
-                            requesterId, ce.getMessage() );
-                }
-
-                // UPDATE LOCALS IF A REQUEST COMES FROM A CLUSTER
-                // IF LOCAL CLUSTER CONSISTENCY IS CONFIGURED
-                if ( !fromCluster || ( fromCluster && remoteCacheServerAttributes.isLocalClusterConsistency() ) )
-                {
-                    ICacheEventQueue<K, V>[] qlist = getEventQList( cacheDesc, requesterId );
-                    log.debug( "qlist.length = {0}", qlist.length );
-                    for ( int i = 0; i < qlist.length; i++ )
-                    {
-                        qlist[i].addPutEvent( item );
-                    }
-                }
-            }
-        }
-        catch ( IOException e )
-        {
-            if ( cacheEventLogger != null )
-            {
-                cacheEventLogger.logError( "RemoteCacheServer", ICacheEventLogger.UPDATE_EVENT, e.getMessage()
-                    + " REGION: " + item.getCacheName() + " ITEM: " + item );
-            }
-
-            log.error( "Trouble in Update. requesterId [{0}]", requesterId, e );
-        }
-
-        // TODO use JAMON for timing
-        log.debug( "put took {0} ms.", () -> timer.getElapsedTime());
-    }
-
-    /**
-     * Log some details.
-     * <p>
-     * @param item
-     */
-    private void logUpdateInfo( ICacheElement<K, V> item )
-    {
-        // not thread safe, but it doesn't have to be 100% accurate
-        puts++;
-
-        if ( log.isInfoEnabled() )
-        {
-            if ( puts % logInterval == 0 )
-            {
-                log.info( "puts = {0}", puts );
-            }
-        }
-
-        log.debug( "In update, put [{0}] in [{1}]",
-                () -> item.getKey(), () -> item.getCacheName() );
-    }
-
-    /**
-     * Returns a cache value from the specified remote cache; or null if the cache or key does not
-     * exist.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @return ICacheElement
-     * @throws IOException
-     */
-    @Override
-    public ICacheElement<K, V> get( String cacheName, K key )
-        throws IOException
-    {
-        return this.get( cacheName, key, 0 );
-    }
-
-    /**
-     * Returns a cache bean from the specified cache; or null if the key does not exist.
-     * <p>
-     * Adding the requestor id, allows the cache to determine the source of the get.
-     * <p>
-     * The internal processing is wrapped in event logging calls.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @param requesterId
-     * @return ICacheElement
-     * @throws IOException
-     */
-    @Override
-    public ICacheElement<K, V> get( String cacheName, K key, long requesterId )
-        throws IOException
-    {
-        ICacheElement<K, V> element = null;
-        ICacheEvent<K> cacheEvent = createICacheEvent( cacheName, key, requesterId, ICacheEventLogger.GET_EVENT );
-        try
-        {
-            element = processGet( cacheName, key, requesterId );
-        }
-        finally
-        {
-            logICacheEvent( cacheEvent );
-        }
-        return element;
-    }
-
-    /**
-     * Returns a cache bean from the specified cache; or null if the key does not exist.
-     * <p>
-     * Adding the requester id, allows the cache to determine the source of the get.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @param requesterId
-     * @return ICacheElement
-     */
-    private ICacheElement<K, V> processGet( String cacheName, K key, long requesterId )
-    {
-        boolean fromCluster = isRequestFromCluster( requesterId );
-
-        log.debug( "get [{0}] from cache [{1}] requesterId = [{2}] fromCluster = {3}",
-                key, cacheName, requesterId, fromCluster );
-
-        CacheListeners<K, V> cacheDesc = getCacheListeners( cacheName );
-
-        ICacheElement<K, V> element = getFromCacheListeners( key, fromCluster, cacheDesc, null );
-        return element;
-    }
-
-    /**
-     * Gets the item from the associated cache listeners.
-     * <p>
-     * @param key
-     * @param fromCluster
-     * @param cacheDesc
-     * @param element
-     * @return ICacheElement
-     */
-    private ICacheElement<K, V> getFromCacheListeners( K key, boolean fromCluster, CacheListeners<K, V> cacheDesc,
-                                                 ICacheElement<K, V> element )
-    {
-        ICacheElement<K, V> returnElement = element;
-
-        if ( cacheDesc != null )
-        {
-            CompositeCache<K, V> c = (CompositeCache<K, V>) cacheDesc.cache;
-
-            // If we have a get come in from a client and we don't have the item
-            // locally, we will allow the cache to look in other non local sources,
-            // such as a remote cache or a lateral.
-            //
-            // Since remote servers never get from clients and clients never go
-            // remote from a remote call, this
-            // will not result in any loops.
-            //
-            // This is the only instance I can think of where we allow a remote get
-            // from a remote call. The purpose is to allow remote cache servers to
-            // talk to each other. If one goes down, you want it to be able to get
-            // data from those that were up when the failed server comes back o
-            // line.
-
-            if ( !fromCluster && this.remoteCacheServerAttributes.isAllowClusterGet() )
-            {
-                log.debug( "NonLocalGet. fromCluster [{0}] AllowClusterGet [{1}]",
-                        fromCluster, this.remoteCacheServerAttributes.isAllowClusterGet() );
-                returnElement = c.get( key );
-            }
-            else
-            {
-                // Gets from cluster type remote will end up here.
-                // Gets from all clients will end up here if allow cluster get is
-                // false.
-                log.debug( "LocalGet. fromCluster [{0}] AllowClusterGet [{1}]",
-                        fromCluster, this.remoteCacheServerAttributes.isAllowClusterGet() );
-                returnElement = c.localGet( key );
-            }
-        }
-
-        return returnElement;
-    }
-
-    /**
-     * Gets all matching items.
-     * <p>
-     * @param cacheName
-     * @param pattern
-     * @return Map of keys and wrapped objects
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern )
-        throws IOException
-    {
-        return getMatching( cacheName, pattern, 0 );
-    }
-
-    /**
-     * Retrieves all matching keys.
-     * <p>
-     * @param cacheName
-     * @param pattern
-     * @param requesterId
-     * @return Map of keys and wrapped objects
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern, long requesterId )
-        throws IOException
-    {
-        ICacheEvent<String> cacheEvent = createICacheEvent( cacheName, pattern, requesterId,
-                                                    ICacheEventLogger.GETMATCHING_EVENT );
-        try
-        {
-            return processGetMatching( cacheName, pattern, requesterId );
-        }
-        finally
-        {
-            logICacheEvent( cacheEvent );
-        }
-    }
-
-    /**
-     * Retrieves all matching keys.
-     * <p>
-     * @param cacheName
-     * @param pattern
-     * @param requesterId
-     * @return Map of keys and wrapped objects
-     */
-    protected Map<K, ICacheElement<K, V>> processGetMatching( String cacheName, String pattern, long requesterId )
-    {
-        boolean fromCluster = isRequestFromCluster( requesterId );
-
-        log.debug( "getMatching [{0}] from cache [{1}] requesterId = [{2}] fromCluster = {3}",
-                pattern, cacheName, requesterId, fromCluster );
-
-        CacheListeners<K, V> cacheDesc = null;
-        try
-        {
-            cacheDesc = getCacheListeners( cacheName );
-        }
-        catch ( Exception e )
-        {
-            log.error( "Problem getting listeners.", e );
-
-            if ( cacheEventLogger != null )
-            {
-                cacheEventLogger.logError( "RemoteCacheServer", ICacheEventLogger.GETMATCHING_EVENT, e.getMessage()
-                    + cacheName + " pattern: " + pattern );
-            }
-        }
-
-        return getMatchingFromCacheListeners( pattern, fromCluster, cacheDesc );
-    }
-
-    /**
-     * Gets the item from the associated cache listeners.
-     * <p>
-     * @param pattern
-     * @param fromCluster
-     * @param cacheDesc
-     * @return Map of keys to results
-     */
-    private Map<K, ICacheElement<K, V>> getMatchingFromCacheListeners( String pattern, boolean fromCluster, CacheListeners<K, V> cacheDesc )
-    {
-        Map<K, ICacheElement<K, V>> elements = null;
-        if ( cacheDesc != null )
-        {
-            CompositeCache<K, V> c = (CompositeCache<K, V>) cacheDesc.cache;
-
-            // We always want to go remote and then merge the items.  But this can lead to inconsistencies after
-            // failover recovery.  Removed items may show up.  There is no good way to prevent this.
-            // We should make it configurable.
-
-            if ( !fromCluster && this.remoteCacheServerAttributes.isAllowClusterGet() )
-            {
-                log.debug( "NonLocalGetMatching. fromCluster [{0}] AllowClusterGet [{1}]",
-                        fromCluster, this.remoteCacheServerAttributes.isAllowClusterGet() );
-                elements = c.getMatching( pattern );
-            }
-            else
-            {
-                // Gets from cluster type remote will end up here.
-                // Gets from all clients will end up here if allow cluster get is
-                // false.
-
-                log.debug( "LocalGetMatching. fromCluster [{0}] AllowClusterGet [{1}]",
-                        fromCluster, this.remoteCacheServerAttributes.isAllowClusterGet() );
-                elements = c.localGetMatching( pattern );
-            }
-        }
-        return elements;
-    }
-
-    /**
-     * Gets multiple items from the cache based on the given set of keys.
-     * <p>
-     * @param cacheName
-     * @param keys
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache for any of these keys
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys )
-        throws IOException
-    {
-        return this.getMultiple( cacheName, keys, 0 );
-    }
-
-    /**
-     * Gets multiple items from the cache based on the given set of keys.
-     * <p>
-     * The internal processing is wrapped in event logging calls.
-     * <p>
-     * @param cacheName
-     * @param keys
-     * @param requesterId
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache for any of these keys
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys, long requesterId )
-        throws IOException
-    {
-        ICacheEvent<Serializable> cacheEvent = createICacheEvent( cacheName, (Serializable) keys, requesterId,
-                                                    ICacheEventLogger.GETMULTIPLE_EVENT );
-        try
-        {
-            return processGetMultiple( cacheName, keys, requesterId );
-        }
-        finally
-        {
-            logICacheEvent( cacheEvent );
-        }
-    }
-
-    /**
-     * Gets multiple items from the cache based on the given set of keys.
-     * <p>
-     * @param cacheName
-     * @param keys
-     * @param requesterId
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache for any of these keys
-     */
-    private Map<K, ICacheElement<K, V>> processGetMultiple( String cacheName, Set<K> keys, long requesterId )
-    {
-        boolean fromCluster = isRequestFromCluster( requesterId );
-
-        log.debug( "getMultiple [{0}] from cache [{1}] requesterId = [{2}] fromCluster = {3}",
-                keys, cacheName, requesterId, fromCluster );
-
-        CacheListeners<K, V> cacheDesc = getCacheListeners( cacheName );
-        Map<K, ICacheElement<K, V>> elements = getMultipleFromCacheListeners( keys, null, fromCluster, cacheDesc );
-        return elements;
-    }
-
-    /**
-     * Since a non-receiving remote cache client will not register a listener, it will not have a
-     * listener id assigned from the server. As such the remote server cannot determine if it is a
-     * cluster or a normal client. It will assume that it is a normal client.
-     * <p>
-     * @param requesterId
-     * @return true is from a cluster.
-     */
-    private boolean isRequestFromCluster( long requesterId )
-    {
-        RemoteType remoteTypeL = idTypeMap.get( Long.valueOf( requesterId ) );
-        return remoteTypeL == RemoteType.CLUSTER;
-    }
-
-    /**
-     * Gets the items from the associated cache listeners.
-     * <p>
-     * @param keys
-     * @param elements
-     * @param fromCluster
-     * @param cacheDesc
-     * @return Map
-     */
-    private Map<K, ICacheElement<K, V>> getMultipleFromCacheListeners( Set<K> keys, Map<K, ICacheElement<K, V>> elements, boolean fromCluster, CacheListeners<K, V> cacheDesc )
-    {
-        Map<K, ICacheElement<K, V>> returnElements = elements;
-
-        if ( cacheDesc != null )
-        {
-            CompositeCache<K, V> c = (CompositeCache<K, V>) cacheDesc.cache;
-
-            // If we have a getMultiple come in from a client and we don't have the item
-            // locally, we will allow the cache to look in other non local sources,
-            // such as a remote cache or a lateral.
-            //
-            // Since remote servers never get from clients and clients never go
-            // remote from a remote call, this
-            // will not result in any loops.
-            //
-            // This is the only instance I can think of where we allow a remote get
-            // from a remote call. The purpose is to allow remote cache servers to
-            // talk to each other. If one goes down, you want it to be able to get
-            // data from those that were up when the failed server comes back on
-            // line.
-
-            if ( !fromCluster && this.remoteCacheServerAttributes.isAllowClusterGet() )
-            {
-                log.debug( "NonLocalGetMultiple. fromCluster [{0}] AllowClusterGet [{1}]",
-                        fromCluster, this.remoteCacheServerAttributes.isAllowClusterGet() );
-
-                returnElements = c.getMultiple( keys );
-            }
-            else
-            {
-                // Gets from cluster type remote will end up here.
-                // Gets from all clients will end up here if allow cluster get is
-                // false.
-
-                log.debug( "LocalGetMultiple. fromCluster [{0}] AllowClusterGet [{1}]",
-                        fromCluster, this.remoteCacheServerAttributes.isAllowClusterGet() );
-
-                returnElements = c.localGetMultiple( keys );
-            }
-        }
-
-        return returnElements;
-    }
-
-    /**
-     * Return the keys in the cache.
-     * <p>
-     * @param cacheName the name of the cache region
-     * @see org.apache.commons.jcs.auxiliary.AuxiliaryCache#getKeySet()
-     */
-    @Override
-    public Set<K> getKeySet(String cacheName) throws IOException
-    {
-        return processGetKeySet( cacheName );
-    }
-
-    /**
-     * Gets the set of keys of objects currently in the cache.
-     * <p>
-     * @param cacheName
-     * @return Set
-     */
-    protected Set<K> processGetKeySet( String cacheName )
-    {
-        CacheListeners<K, V> cacheDesc = getCacheListeners( cacheName );
-
-        if ( cacheDesc == null )
-        {
-            return Collections.emptySet();
-        }
-
-        CompositeCache<K, V> c = (CompositeCache<K, V>) cacheDesc.cache;
-        return c.getKeySet();
-    }
-
-    /**
-     * Removes the given key from the specified remote cache. Defaults the listener id to 0.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @throws IOException
-     */
-    @Override
-    public void remove( String cacheName, K key )
-        throws IOException
-    {
-        remove( cacheName, key, 0 );
-    }
-
-    /**
-     * Remove the key from the cache region and don't tell the source listener about it.
-     * <p>
-     * The internal processing is wrapped in event logging calls.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @param requesterId
-     * @throws IOException
-     */
-    @Override
-    public void remove( String cacheName, K key, long requesterId )
-        throws IOException
-    {
-        ICacheEvent<K> cacheEvent = createICacheEvent( cacheName, key, requesterId, ICacheEventLogger.REMOVE_EVENT );
-        try
-        {
-            processRemove( cacheName, key, requesterId );
-        }
-        finally
-        {
-            logICacheEvent( cacheEvent );
-        }
-    }
-
-    /**
-     * Remove the key from the cache region and don't tell the source listener about it.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @param requesterId
-     * @throws IOException
-     */
-    private void processRemove( String cacheName, K key, long requesterId )
-        throws IOException
-    {
-        log.debug( "remove [{0}] from cache [{1}]", key, cacheName );
-
-        CacheListeners<K, V> cacheDesc = cacheListenersMap.get( cacheName );
-
-        boolean fromCluster = isRequestFromCluster( requesterId );
-
-        if ( cacheDesc != null )
-        {
-            // best attempt to achieve ordered cache item removal and
-            // notification.
-            synchronized ( cacheDesc )
-            {
-                boolean removeSuccess = false;
-
-                // No need to notify if it was not cached.
-                CompositeCache<K, V> c = (CompositeCache<K, V>) cacheDesc.cache;
-
-                if ( fromCluster )
-                {
-                    log.debug( "Remove FROM cluster, NOT updating other auxiliaries for region" );
-                    removeSuccess = c.localRemove( key );
-                }
-                else
-                {
-                    log.debug( "Remove NOT from cluster, updating other auxiliaries for region" );
-                    removeSuccess = c.remove( key );
-                }
-
-                log.debug( "remove [{0}] from cache [{1}] success (was it found) = {2}",
-                        key, cacheName, removeSuccess );
-
-                // UPDATE LOCALS IF A REQUEST COMES FROM A CLUSTER
-                // IF LOCAL CLUSTER CONSISTENCY IS CONFIGURED
-                if ( !fromCluster || ( fromCluster && remoteCacheServerAttributes.isLocalClusterConsistency() ) )
-                {
-                    ICacheEventQueue<K, V>[] qlist = getEventQList( cacheDesc, requesterId );
-
-                    for ( int i = 0; i < qlist.length; i++ )
-                    {
-                        qlist[i].addRemoveEvent( key );
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Remove all keys from the specified remote cache.
-     * <p>
-     * @param cacheName
-     * @throws IOException
-     */
-    @Override
-    public void removeAll( String cacheName )
-        throws IOException
-    {
-        removeAll( cacheName, 0 );
-    }
-
-    /**
-     * Remove all keys from the specified remote cache.
-     * <p>
-     * The internal processing is wrapped in event logging calls.
-     * <p>
-     * @param cacheName
-     * @param requesterId
-     * @throws IOException
-     */
-    @Override
-    public void removeAll( String cacheName, long requesterId )
-        throws IOException
-    {
-        ICacheEvent<String> cacheEvent = createICacheEvent( cacheName, "all", requesterId, ICacheEventLogger.REMOVEALL_EVENT );
-        try
-        {
-            processRemoveAll( cacheName, requesterId );
-        }
-        finally
-        {
-            logICacheEvent( cacheEvent );
-        }
-    }
-
-    /**
-     * Remove all keys from the specified remote cache.
-     * <p>
-     * @param cacheName
-     * @param requesterId
-     * @throws IOException
-     */
-    private void processRemoveAll( String cacheName, long requesterId )
-        throws IOException
-    {
-        CacheListeners<K, V> cacheDesc = cacheListenersMap.get( cacheName );
-
-        boolean fromCluster = isRequestFromCluster( requesterId );
-
-        if ( cacheDesc != null )
-        {
-            // best attempt to achieve ordered cache item removal and
-            // notification.
-            synchronized ( cacheDesc )
-            {
-                // No need to broadcast, or notify if it was not cached.
-                CompositeCache<K, V> c = (CompositeCache<K, V>) cacheDesc.cache;
-
-                if ( fromCluster )
-                {
-                    log.debug( "RemoveALL FROM cluster, NOT updating other auxiliaries for region" );
-                    c.localRemoveAll();
-                }
-                else
-                {
-                    log.debug( "RemoveALL NOT from cluster, updating other auxiliaries for region" );
-                    c.removeAll();
-                }
-
-                // update registered listeners
-                if ( !fromCluster || ( fromCluster && remoteCacheServerAttributes.isLocalClusterConsistency() ) )
-                {
-                    ICacheEventQueue<K, V>[] qlist = getEventQList( cacheDesc, requesterId );
-
-                    for ( int i = 0; i < qlist.length; i++ )
-                    {
-                        qlist[i].addRemoveAllEvent();
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * How many put events have we received.
-     * <p>
-     * @return puts
-     */
-    // Currently only intended for use by unit tests
-    int getPutCount()
-    {
-        return puts;
-    }
-
-    /**
-     * Frees the specified remote cache.
-     * <p>
-     * @param cacheName
-     * @throws IOException
-     */
-    @Override
-    public void dispose( String cacheName )
-        throws IOException
-    {
-        dispose( cacheName, 0 );
-    }
-
-    /**
-     * Frees the specified remote cache.
-     * <p>
-     * @param cacheName
-     * @param requesterId
-     * @throws IOException
-     */
-    public void dispose( String cacheName, long requesterId )
-        throws IOException
-    {
-        ICacheEvent<String> cacheEvent = createICacheEvent( cacheName, "none", requesterId, ICacheEventLogger.DISPOSE_EVENT );
-        try
-        {
-            processDispose( cacheName, requesterId );
-        }
-        finally
-        {
-            logICacheEvent( cacheEvent );
-        }
-    }
-
-    /**
-     * @param cacheName
-     * @param requesterId
-     * @throws IOException
-     */
-    private void processDispose( String cacheName, long requesterId )
-        throws IOException
-    {
-        log.info( "Dispose request received from listener [{0}]", requesterId );
-
-        CacheListeners<K, V> cacheDesc = cacheListenersMap.get( cacheName );
-
-        // this is dangerous
-        if ( cacheDesc != null )
-        {
-            // best attempt to achieve ordered free-cache-op and notification.
-            synchronized ( cacheDesc )
-            {
-                ICacheEventQueue<K, V>[] qlist = getEventQList( cacheDesc, requesterId );
-
-                for ( int i = 0; i < qlist.length; i++ )
-                {
-                    qlist[i].addDisposeEvent();
-                }
-                cacheManager.freeCache( cacheName );
-            }
-        }
-    }
-
-    /**
-     * Frees all remote caches.
-     * <p>
-     * @throws IOException
-     */
-    @Override
-    public void release()
-        throws IOException
-    {
-        for (CacheListeners<K, V> cacheDesc : cacheListenersMap.values())
-        {
-            ICacheEventQueue<K, V>[] qlist = getEventQList( cacheDesc, 0 );
-
-            for ( int i = 0; i < qlist.length; i++ )
-            {
-                qlist[i].addDisposeEvent();
-            }
-        }
-        cacheManager.release();
-    }
-
-    /**
-     * Returns the cache listener for the specified cache. Creates the cache and the cache
-     * descriptor if they do not already exist.
-     * <p>
-     * @param cacheName
-     * @return The cacheListeners value
-     */
-    protected CacheListeners<K, V> getCacheListeners( String cacheName )
-    {
-        CacheListeners<K, V> cacheListeners = cacheListenersMap.computeIfAbsent(cacheName, key -> {
-            CompositeCache<K, V> cache = cacheManager.getCache(key);
-            return new CacheListeners<>( cache );
-        });
-
-        return cacheListeners;
-    }
-
-    /**
-     * Gets the clusterListeners attribute of the RemoteCacheServer object.
-     * <p>
-     * TODO may be able to remove this
-     * @param cacheName
-     * @return The clusterListeners value
-     */
-    protected CacheListeners<K, V> getClusterListeners( String cacheName )
-    {
-        CacheListeners<K, V> cacheListeners = clusterListenersMap.computeIfAbsent(cacheName, key -> {
-            CompositeCache<K, V> cache = cacheManager.getCache( cacheName );
-            return new CacheListeners<>( cache );
-        });
-
-        return cacheListeners;
-    }
-
-    /**
-     * Gets the eventQList attribute of the RemoteCacheServer object. This returns the event queues
-     * stored in the cacheListeners object for a particular region, if the queue is not for this
-     * requester.
-     * <p>
-     * Basically, this makes sure that a request from a particular local cache, identified by its
-     * listener id, does not result in a call to that same listener.
-     * <p>
-     * @param cacheListeners
-     * @param requesterId
-     * @return The eventQList value
-     */
-    @SuppressWarnings("unchecked") // No generic arrays in java
-    private ICacheEventQueue<K, V>[] getEventQList( CacheListeners<K, V> cacheListeners, long requesterId )
-    {
-        ICacheEventQueue<K, V>[] list = cacheListeners.eventQMap.values().toArray( new ICacheEventQueue[0] );
-        int count = 0;
-        // Set those not qualified to null; Count those qualified.
-        for ( int i = 0; i < list.length; i++ )
-        {
-            ICacheEventQueue<K, V> q = list[i];
-            if ( q.isWorking() && q.getListenerId() != requesterId )
-            {
-                count++;
-            }
-            else
-            {
-                list[i] = null;
-            }
-        }
-        if ( count == list.length )
-        {
-            // All qualified.
-            return list;
-        }
-
-        // Returns only the qualified.
-        ICacheEventQueue<K, V>[] qq = new ICacheEventQueue[count];
-        count = 0;
-        for ( int i = 0; i < list.length; i++ )
-        {
-            if ( list[i] != null )
-            {
-                qq[count++] = list[i];
-            }
-        }
-        return qq;
-    }
-
-    /**
-     * Removes dead event queues. Should clean out deregistered listeners.
-     * <p>
-     * @param eventQMap
-     */
-    private static <KK, VV> void cleanupEventQMap( Map<Long, ICacheEventQueue<KK, VV>> eventQMap )
-    {
-        // this does not care if the q is alive (i.e. if
-        // there are active threads; it cares if the queue
-        // is working -- if it has not encountered errors
-        // above the failure threshold
-        eventQMap.entrySet().removeIf(e -> !e.getValue().isWorking());
-    }
-
-    /**
-     * Subscribes to the specified remote cache.
-     * <p>
-     * If the client id is 0, then the remote cache server will increment it's local count and
-     * assign an id to the client.
-     * <p>
-     * @param cacheName the specified remote cache.
-     * @param listener object to notify for cache changes. must be synchronized since there are
-     *            remote calls involved.
-     * @throws IOException
-     */
-    @Override
-    @SuppressWarnings("unchecked") // Need to cast to specific return type from getClusterListeners()
-    public <KK, VV> void addCacheListener( String cacheName, ICacheListener<KK, VV> listener )
-        throws IOException
-    {
-        if ( cacheName == null || listener == null )
-        {
-            throw new IllegalArgumentException( "cacheName and listener must not be null" );
-        }
-        CacheListeners<KK, VV> cacheListeners;
-
-        IRemoteCacheListener<KK, VV> ircl = (IRemoteCacheListener<KK, VV>) listener;
-
-        String listenerAddress = ircl.getLocalHostAddress();
-
-        RemoteType remoteType = ircl.getRemoteType();
-        if ( remoteType == RemoteType.CLUSTER )
-        {
-            log.debug( "adding cluster listener, listenerAddress [{0}]", listenerAddress );
-            cacheListeners = (CacheListeners<KK, VV>)getClusterListeners( cacheName );
-        }
-        else
-        {
-            log.debug( "adding normal listener, listenerAddress [{0}]", listenerAddress );
-            cacheListeners = (CacheListeners<KK, VV>)getCacheListeners( cacheName );
-        }
-        Map<Long, ICacheEventQueue<KK, VV>> eventQMap = cacheListeners.eventQMap;
-        cleanupEventQMap( eventQMap );
-
-        // synchronized ( listenerId )
-        synchronized ( ICacheListener.class )
-        {
-            long id = 0;
-            try
-            {
-                id = listener.getListenerId();
-                // clients probably shouldn't do this.
-                if ( id == 0 )
-                {
-                    // must start at one so the next gets recognized
-                    long listenerIdB = nextListenerId();
-                    log.debug( "listener id={0} addded for cache [{1}], listenerAddress [{2}]",
-                            listenerIdB & 0xff, cacheName, listenerAddress );
-                    listener.setListenerId( listenerIdB );
-                    id = listenerIdB;
-
-                    // in case it needs synchronization
-                    String message = "Adding vm listener under new id = [" + listenerIdB + "], listenerAddress ["
-                        + listenerAddress + "]";
-                    logApplicationEvent( "RemoteCacheServer", "addCacheListener", message );
-                    log.info( message );
-                }
-                else
-                {
-                    String message = "Adding listener under existing id = [" + id + "], listenerAddress ["
-                        + listenerAddress + "]";
-                    logApplicationEvent( "RemoteCacheServer", "addCacheListener", message );
-                    log.info( message );
-                    // should confirm the the host is the same as we have on
-                    // record, just in case a client has made a mistake.
-                }
-
-                // relate the type to an id
-                this.idTypeMap.put( Long.valueOf( id ), remoteType);
-                if ( listenerAddress != null )
-                {
-                    this.idIPMap.put( Long.valueOf( id ), listenerAddress );
-                }
-            }
-            catch ( IOException ioe )
-            {
-                String message = "Problem setting listener id, listenerAddress [" + listenerAddress + "]";
-                log.error( message, ioe );
-
-                if ( cacheEventLogger != null )
-                {
-                    cacheEventLogger.logError( "RemoteCacheServer", "addCacheListener", message + " - "
-                        + ioe.getMessage() );
-                }
-            }
-
-            CacheEventQueueFactory<KK, VV> fact = new CacheEventQueueFactory<>();
-            ICacheEventQueue<KK, VV> q = fact.createCacheEventQueue( listener, id, cacheName, remoteCacheServerAttributes
-                .getEventQueuePoolName(), remoteCacheServerAttributes.getEventQueueType() );
-
-            eventQMap.put(Long.valueOf(listener.getListenerId()), q);
-
-            log.info( cacheListeners );
-        }
-    }
-
-    /**
-     * Subscribes to all remote caches.
-     * <p>
-     * @param listener The feature to be added to the CacheListener attribute
-     * @throws IOException
-     */
-    @Override
-    public <KK, VV> void addCacheListener( ICacheListener<KK, VV> listener )
-        throws IOException
-    {
-        for (String cacheName : cacheListenersMap.keySet())
-        {
-            addCacheListener( cacheName, listener );
-
-            log.debug( "Adding listener for cache [{0}]", cacheName );
-        }
-    }
-
-    /**
-     * Unsubscribe this listener from this region. If the listener is registered, it will be removed
-     * from the event queue map list.
-     * <p>
-     * @param cacheName
-     * @param listener
-     * @throws IOException
-     */
-    @Override
-    public <KK, VV> void removeCacheListener( String cacheName, ICacheListener<KK, VV> listener )
-        throws IOException
-    {
-        removeCacheListener( cacheName, listener.getListenerId() );
-    }
-
-    /**
-     * Unsubscribe this listener from this region. If the listener is registered, it will be removed
-     * from the event queue map list.
-     * <p>
-     * @param cacheName
-     * @param listenerId
-     */
-    public void removeCacheListener( String cacheName, long listenerId )
-    {
-        String message = "Removing listener for cache region = [" + cacheName + "] and listenerId [" + listenerId + "]";
-        logApplicationEvent( "RemoteCacheServer", "removeCacheListener", message );
-        log.info( message );
-
-        boolean isClusterListener = isRequestFromCluster( listenerId );
-
-        CacheListeners<K, V> cacheDesc = null;
-
-        if ( isClusterListener )
-        {
-            cacheDesc = getClusterListeners( cacheName );
-        }
-        else
-        {
-            cacheDesc = getCacheListeners( cacheName );
-        }
-        Map<Long, ICacheEventQueue<K, V>> eventQMap = cacheDesc.eventQMap;
-        cleanupEventQMap( eventQMap );
-        ICacheEventQueue<K, V> q = eventQMap.remove( Long.valueOf( listenerId ) );
-
-        if ( q != null )
-        {
-            log.debug( "Found queue for cache region = [{0}] and listenerId [{1}]",
-                    cacheName, listenerId );
-            q.destroy();
-            cleanupEventQMap( eventQMap );
-        }
-        else
-        {
-            log.debug( "Did not find queue for cache region = [{0}] and listenerId [{1}]",
-                    cacheName, listenerId );
-        }
-
-        // cleanup
-        idTypeMap.remove( Long.valueOf( listenerId ) );
-        idIPMap.remove( Long.valueOf( listenerId ) );
-
-        log.info( "After removing listener [{0}] cache region {1} listener size [{2}]",
-                listenerId, cacheName, eventQMap.size() );
-    }
-
-    /**
-     * Unsubscribes from all remote caches.
-     * <p>
-     * @param listener
-     * @throws IOException
-     */
-    @Override
-    public <KK, VV> void removeCacheListener( ICacheListener<KK, VV> listener )
-        throws IOException
-    {
-        for (String cacheName : cacheListenersMap.keySet())
-        {
-            removeCacheListener( cacheName, listener );
-
-            log.info( "Removing listener for cache [{0}]", cacheName );
-        }
-    }
-
-    /**
-     * Shuts down the remote server.
-     * <p>
-     * @throws IOException
-     */
-    @Override
-    public void shutdown()
-        throws IOException
-    {
-        shutdown("", Registry.REGISTRY_PORT);
-    }
-
-    /**
-     * Shuts down a server at a particular host and port. Then it calls shutdown on the cache
-     * itself.
-     * <p>
-     * @param host
-     * @param port
-     * @throws IOException
-     */
-    @Override
-    public void shutdown( String host, int port )
-        throws IOException
-    {
-        log.info( "Received shutdown request. Shutting down server." );
-
-        synchronized (listenerId)
-        {
-            for (String cacheName : cacheListenersMap.keySet())
-            {
-                for (int i = 0; i <= listenerId[0]; i++)
-                {
-                    removeCacheListener( cacheName, i );
-                }
-
-                log.info( "Removing listener for cache [{0}]", cacheName );
-            }
-
-            cacheListenersMap.clear();
-            clusterListenersMap.clear();
-        }
-        RemoteCacheServerFactory.shutdownImpl( host, port );
-        this.cacheManager.shutDown();
-    }
-
-    /**
-     * Called by the RMI runtime sometime after the runtime determines that the reference list, the
-     * list of clients referencing the remote object, becomes empty.
-     */
-    // TODO: test out the DGC.
-    @Override
-    public void unreferenced()
-    {
-        log.info( "*** Server now unreferenced and subject to GC. ***" );
-    }
-
-    /**
-     * Returns the next generated listener id [0,255].
-     * <p>
-     * @return the listener id of a client. This should be unique for this server.
-     */
-    private long nextListenerId()
-    {
-        long id = 0;
-        if ( listenerId[0] == Integer.MAX_VALUE )
-        {
-            synchronized ( listenerId )
-            {
-                id = listenerId[0];
-                listenerId[0] = 0;
-                // TODO: record & check if the generated id is currently being
-                // used by a valid listener. Currently if the id wraps after
-                // Long.MAX_VALUE,
-                // we just assume it won't collide with an existing listener who
-                // is live.
-            }
-        }
-        else
-        {
-            synchronized ( listenerId )
-            {
-                id = ++listenerId[0];
-            }
-        }
-        return id;
-    }
-
-    /**
-     * Gets the stats attribute of the RemoteCacheServer object.
-     * <p>
-     * @return The stats value
-     * @throws IOException
-     */
-    @Override
-    public String getStats()
-        throws IOException
-    {
-        return cacheManager.getStats();
-    }
-
-    /**
-     * Logs an event if an event logger is configured.
-     * <p>
-     * @param item
-     * @param requesterId
-     * @param eventName
-     * @return ICacheEvent
-     */
-    private ICacheEvent<ICacheElement<K, V>> createICacheEvent( ICacheElement<K, V> item, long requesterId, String eventName )
-    {
-        if ( cacheEventLogger == null )
-        {
-            return new CacheEvent<>();
-        }
-        String ipAddress = getExtraInfoForRequesterId( requesterId );
-        return cacheEventLogger
-            .createICacheEvent( "RemoteCacheServer", item.getCacheName(), eventName, ipAddress, item );
-    }
-
-    /**
-     * Logs an event if an event logger is configured.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @param requesterId
-     * @param eventName
-     * @return ICacheEvent
-     */
-    private <T> ICacheEvent<T> createICacheEvent( String cacheName, T key, long requesterId, String eventName )
-    {
-        if ( cacheEventLogger == null )
-        {
-            return new CacheEvent<>();
-        }
-        String ipAddress = getExtraInfoForRequesterId( requesterId );
-        return cacheEventLogger.createICacheEvent( "RemoteCacheServer", cacheName, eventName, ipAddress, key );
-    }
-
-    /**
-     * Logs an event if an event logger is configured.
-     * <p>
-     * @param source
-     * @param eventName
-     * @param optionalDetails
-     */
-    protected void logApplicationEvent( String source, String eventName, String optionalDetails )
-    {
-        if ( cacheEventLogger != null )
-        {
-            cacheEventLogger.logApplicationEvent( source, eventName, optionalDetails );
-        }
-    }
-
-    /**
-     * Logs an event if an event logger is configured.
-     * <p>
-     * @param cacheEvent
-     */
-    protected <T> void logICacheEvent( ICacheEvent<T> cacheEvent )
-    {
-        if ( cacheEventLogger != null )
-        {
-            cacheEventLogger.logICacheEvent( cacheEvent );
-        }
-    }
-
-    /**
-     * Ip address for the client, if one is stored.
-     * <p>
-     * Protected for testing.
-     * <p>
-     * @param requesterId
-     * @return String
-     */
-    protected String getExtraInfoForRequesterId( long requesterId )
-    {
-        String ipAddress = idIPMap.get( Long.valueOf( requesterId ) );
-        return ipAddress;
-    }
-
-    /**
-     * Allows it to be injected.
-     * <p>
-     * @param cacheEventLogger
-     */
-    public void setCacheEventLogger( ICacheEventLogger cacheEventLogger )
-    {
-        this.cacheEventLogger = cacheEventLogger;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/server/RemoteCacheServerAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/server/RemoteCacheServerAttributes.java
deleted file mode 100644
index 45a0a72..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/server/RemoteCacheServerAttributes.java
+++ /dev/null
@@ -1,212 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.server;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.remote.CommonRemoteCacheAttributes;
-import org.apache.commons.jcs.auxiliary.remote.server.behavior.IRemoteCacheServerAttributes;
-
-/**
- * These attributes are used to configure the remote cache server.
- */
-public class RemoteCacheServerAttributes
-    extends CommonRemoteCacheAttributes
-    implements IRemoteCacheServerAttributes
-{
-    /** Don't change */
-    private static final long serialVersionUID = -2741662082869155365L;
-
-    /** port the server will listen to */
-    private int servicePort = 0;
-
-    /** Can a cluster remote get from other remotes */
-    private boolean allowClusterGet = true;
-
-    /** The config file, the initialization is multistage. Remote cache then composite cache. */
-    private String configFileName = "";
-
-    /** Should we start the registry */
-    private boolean DEFAULT_START_REGISTRY = true;
-
-    /** Should we start the registry */
-    private boolean startRegistry = DEFAULT_START_REGISTRY;
-
-    /** Should we try to keep the registry alive */
-    private boolean DEFAULT_USE_REGISTRY_KEEP_ALIVE = true;
-
-    /** Should we try to keep the registry alive */
-    private boolean useRegistryKeepAlive = DEFAULT_USE_REGISTRY_KEEP_ALIVE;
-
-    /** The delay between runs */
-    private long registryKeepAliveDelayMillis = 15 * 1000;
-
-    /** Default constructor for the RemoteCacheAttributes object */
-    public RemoteCacheServerAttributes()
-    {
-        super();
-    }
-
-    /**
-     * Gets the localPort attribute of the RemoteCacheAttributes object
-     * <p>
-     * @return The localPort value
-     */
-    @Override
-    public int getServicePort()
-    {
-        return this.servicePort;
-    }
-
-    /**
-     * Sets the localPort attribute of the RemoteCacheAttributes object
-     * <p>
-     * @param p The new localPort value
-     */
-    @Override
-    public void setServicePort( int p )
-    {
-        this.servicePort = p;
-    }
-
-    /**
-     * Should gets from non-cluster clients be allowed to get from other remote auxiliaries.
-     * <p>
-     * @return The localClusterConsistency value
-     */
-    @Override
-    public boolean isAllowClusterGet()
-    {
-        return allowClusterGet;
-    }
-
-    /**
-     * Should we try to get from other cluster servers if we don't find the items locally.
-     * <p>
-     * @param r The new localClusterConsistency value
-     */
-    @Override
-    public void setAllowClusterGet( boolean r )
-    {
-        allowClusterGet = r;
-    }
-
-    /**
-     * Gets the ConfigFileName attribute of the IRemoteCacheAttributes object
-     * <p>
-     * @return The clusterServers value
-     */
-    @Override
-    public String getConfigFileName()
-    {
-        return configFileName;
-    }
-
-    /**
-     * Sets the ConfigFileName attribute of the IRemoteCacheAttributes object
-     * <p>
-     * @param s The new clusterServers value
-     */
-    @Override
-    public void setConfigFileName( String s )
-    {
-        configFileName = s;
-    }
-
-    /**
-     * Should we try to keep the registry alive
-     * <p>
-     * @param useRegistryKeepAlive the useRegistryKeepAlive to set
-     */
-    @Override
-    public void setUseRegistryKeepAlive( boolean useRegistryKeepAlive )
-    {
-        this.useRegistryKeepAlive = useRegistryKeepAlive;
-    }
-
-    /**
-     * Should we start the registry
-     * <p>
-     * @param startRegistry the startRegistry to set
-     * @deprecated Always true, to be removed
-     */
-    @Override
-    public void setStartRegistry( boolean startRegistry )
-    {
-        this.startRegistry = startRegistry;
-    }
-
-    /**
-     * Should we start the registry
-     * <p>
-     * @return the startRegistry
-     * @deprecated Always true, to be removed
-     */
-    @Override
-    public boolean isStartRegistry()
-    {
-        return startRegistry;
-    }
-
-    /**
-     * Should we try to keep the registry alive
-     * <p>
-     * @return the useRegistryKeepAlive
-     */
-    @Override
-    public boolean isUseRegistryKeepAlive()
-    {
-        return useRegistryKeepAlive;
-    }
-
-    /**
-     * @param registryKeepAliveDelayMillis the registryKeepAliveDelayMillis to set
-     */
-    @Override
-    public void setRegistryKeepAliveDelayMillis( long registryKeepAliveDelayMillis )
-    {
-        this.registryKeepAliveDelayMillis = registryKeepAliveDelayMillis;
-    }
-
-    /**
-     * @return the registryKeepAliveDelayMillis
-     */
-    @Override
-    public long getRegistryKeepAliveDelayMillis()
-    {
-        return registryKeepAliveDelayMillis;
-    }
-
-    /**
-     * @return String details
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder buf = new StringBuilder(super.toString());
-        buf.append( "\n servicePort = [" + this.getServicePort() + "]" );
-        buf.append( "\n allowClusterGet = [" + this.isAllowClusterGet() + "]" );
-        buf.append( "\n configFileName = [" + this.getConfigFileName() + "]" );
-        buf.append( "\n rmiSocketFactoryTimeoutMillis = [" + this.getRmiSocketFactoryTimeoutMillis() + "]" );
-        buf.append( "\n useRegistryKeepAlive = [" + this.isUseRegistryKeepAlive() + "]" );
-        buf.append( "\n registryKeepAliveDelayMillis = [" + this.getRegistryKeepAliveDelayMillis() + "]" );
-        buf.append( "\n eventQueueType = [" + this.getEventQueueType() + "]" );
-        buf.append( "\n eventQueuePoolName = [" + this.getEventQueuePoolName() + "]" );
-        return buf.toString();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/server/RemoteCacheServerFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/server/RemoteCacheServerFactory.java
deleted file mode 100644
index 1610ad5..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/server/RemoteCacheServerFactory.java
+++ /dev/null
@@ -1,487 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.server;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.rmi.Naming;
-import java.rmi.NotBoundException;
-import java.rmi.Remote;
-import java.rmi.RemoteException;
-import java.rmi.registry.Registry;
-import java.rmi.server.RMISocketFactory;
-import java.rmi.server.UnicastRemoteObject;
-import java.util.Properties;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheConfigurator;
-import org.apache.commons.jcs.auxiliary.remote.RemoteUtils;
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheConstants;
-import org.apache.commons.jcs.engine.behavior.ICacheServiceAdmin;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-import org.apache.commons.jcs.utils.config.OptionConverter;
-import org.apache.commons.jcs.utils.config.PropertySetter;
-import org.apache.commons.jcs.utils.threadpool.DaemonThreadFactory;
-
-/**
- * Provides remote cache services. This creates remote cache servers and can proxy command line
- * requests to a running server.
- */
-public class RemoteCacheServerFactory
-    implements IRemoteCacheConstants
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog( RemoteCacheServerFactory.class );
-
-    /** The single instance of the RemoteCacheServer object. */
-    private static RemoteCacheServer<?, ?> remoteCacheServer;
-
-    /** The name of the service. */
-    private static String serviceName = IRemoteCacheConstants.REMOTE_CACHE_SERVICE_VAL;
-
-    /** Executes the registry keep alive. */
-    private static ScheduledExecutorService keepAliveDaemon;
-
-    /** A reference to the registry. */
-    private static Registry registry = null;
-
-    /** Constructor for the RemoteCacheServerFactory object. */
-    private RemoteCacheServerFactory()
-    {
-        super();
-    }
-
-    /**
-     * This will allow you to get stats from the server, etc. Perhaps we should provide methods on
-     * the factory to do this instead.
-     * <p>
-     * A remote cache is either a local cache or a cluster cache.
-     * </p>
-     * @return Returns the remoteCacheServer.
-     */
-    @SuppressWarnings("unchecked") // Need cast to specific RemoteCacheServer
-    public static <K, V> RemoteCacheServer<K, V> getRemoteCacheServer()
-    {
-        return (RemoteCacheServer<K, V>)remoteCacheServer;
-    }
-
-    // ///////////////////// Startup/shutdown methods. //////////////////
-    /**
-     * Starts up the remote cache server on this JVM, and binds it to the registry on the given host
-     * and port.
-     * <p>
-     * A remote cache is either a local cache or a cluster cache.
-     * <p>
-     * @param host
-     * @param port
-     * @param props
-     * @throws IOException
-     */
-    public static void startup( String host, int port, Properties props)
-        throws IOException
-    {
-        if ( remoteCacheServer != null )
-        {
-            throw new IllegalArgumentException( "Server already started." );
-        }
-
-        synchronized ( RemoteCacheServer.class )
-        {
-            if ( remoteCacheServer != null )
-            {
-                return;
-            }
-            if ( host == null )
-            {
-                host = "";
-            }
-
-            RemoteCacheServerAttributes rcsa = configureRemoteCacheServerAttributes(props);
-
-            // These should come from the file!
-            rcsa.setRemoteLocation( host, port );
-            log.info( "Creating server with these attributes: {0}", rcsa );
-
-            setServiceName( rcsa.getRemoteServiceName() );
-
-            RMISocketFactory customRMISocketFactory = configureObjectSpecificCustomFactory( props );
-
-            RemoteUtils.configureGlobalCustomSocketFactory( rcsa.getRmiSocketFactoryTimeoutMillis() );
-
-            // CONFIGURE THE EVENT LOGGER
-            ICacheEventLogger cacheEventLogger = configureCacheEventLogger( props );
-
-            // CREATE SERVER
-            if ( customRMISocketFactory != null )
-            {
-                remoteCacheServer = new RemoteCacheServer<>( rcsa, props, customRMISocketFactory );
-            }
-            else
-            {
-                remoteCacheServer = new RemoteCacheServer<>( rcsa, props );
-            }
-
-            remoteCacheServer.setCacheEventLogger( cacheEventLogger );
-
-            // START THE REGISTRY
-        	registry = RemoteUtils.createRegistry(port);
-
-            // REGISTER THE SERVER
-            registerServer( serviceName, remoteCacheServer );
-
-            // KEEP THE REGISTRY ALIVE
-            if ( rcsa.isUseRegistryKeepAlive() )
-            {
-                if ( keepAliveDaemon == null )
-                {
-                    keepAliveDaemon = Executors.newScheduledThreadPool(1,
-                            new DaemonThreadFactory("JCS-RemoteCacheServerFactory-"));
-                }
-                RegistryKeepAliveRunner runner = new RegistryKeepAliveRunner( host, port, serviceName );
-                runner.setCacheEventLogger( cacheEventLogger );
-                keepAliveDaemon.scheduleAtFixedRate(runner, 0, rcsa.getRegistryKeepAliveDelayMillis(), TimeUnit.MILLISECONDS);
-            }
-        }
-    }
-
-    /**
-     * Tries to get the event logger by new and old config styles.
-     * <p>
-     * @param props
-     * @return ICacheEventLogger
-     */
-    protected static ICacheEventLogger configureCacheEventLogger( Properties props )
-    {
-        ICacheEventLogger cacheEventLogger = AuxiliaryCacheConfigurator
-            .parseCacheEventLogger( props, IRemoteCacheConstants.CACHE_SERVER_PREFIX );
-
-        // try the old way
-        if ( cacheEventLogger == null )
-        {
-            cacheEventLogger = AuxiliaryCacheConfigurator.parseCacheEventLogger( props,
-                                                                                 IRemoteCacheConstants.PROPERTY_PREFIX );
-        }
-        return cacheEventLogger;
-    }
-
-    /**
-     * This configures an object specific custom factory. This will be configured for just this
-     * object in the registry. This can be null.
-     * <p>
-     * @param props
-     * @return RMISocketFactory
-     */
-    protected static RMISocketFactory configureObjectSpecificCustomFactory( Properties props )
-    {
-        RMISocketFactory customRMISocketFactory =
-            OptionConverter.instantiateByKey( props, CUSTOM_RMI_SOCKET_FACTORY_PROPERTY_PREFIX, null );
-
-        if ( customRMISocketFactory != null )
-        {
-            PropertySetter.setProperties( customRMISocketFactory, props, CUSTOM_RMI_SOCKET_FACTORY_PROPERTY_PREFIX
-                + "." );
-            log.info( "Will use server specific custom socket factory. {0}",
-                    customRMISocketFactory );
-        }
-        else
-        {
-            log.info( "No server specific custom socket factory defined." );
-        }
-        return customRMISocketFactory;
-    }
-
-    /**
-     * Registers the server with the registry. I broke this off because we might want to have code
-     * that will restart a dead registry. It will need to rebind the server.
-     * <p>
-     * @param serviceName the name of the service
-     * @param server the server object to bind
-     * @throws RemoteException
-     */
-    protected static void registerServer(String serviceName, Remote server )
-        throws RemoteException
-    {
-        if ( server == null )
-        {
-            throw new RemoteException( "Cannot register the server until it is created." );
-        }
-
-        if ( registry == null )
-        {
-            throw new RemoteException( "Cannot register the server: Registry is null." );
-        }
-
-        log.info( "Binding server to {0}", serviceName );
-
-        registry.rebind( serviceName, server );
-    }
-
-    /**
-     * Configure.
-     * <p>
-     * jcs.remotecache.serverattributes.ATTRIBUTENAME=ATTRIBUTEVALUE
-     * <p>
-     * @param prop
-     * @return RemoteCacheServerAttributesconfigureRemoteCacheServerAttributes
-     */
-    protected static RemoteCacheServerAttributes configureRemoteCacheServerAttributes( Properties prop )
-    {
-        RemoteCacheServerAttributes rcsa = new RemoteCacheServerAttributes();
-
-        // configure automatically
-        PropertySetter.setProperties( rcsa, prop, CACHE_SERVER_ATTRIBUTES_PROPERTY_PREFIX + "." );
-
-        configureManuallyIfValuesArePresent( prop, rcsa );
-
-        return rcsa;
-    }
-
-    /**
-     * This looks for the old config values.
-     * <p>
-     * @param prop
-     * @param rcsa
-     */
-    private static void configureManuallyIfValuesArePresent( Properties prop, RemoteCacheServerAttributes rcsa )
-    {
-        // DEPRECATED CONFIG
-        String servicePortStr = prop.getProperty( REMOTE_CACHE_SERVICE_PORT );
-        if ( servicePortStr != null )
-        {
-            try
-            {
-                int servicePort = Integer.parseInt( servicePortStr );
-                rcsa.setServicePort( servicePort );
-                log.debug( "Remote cache service uses port number {0}", servicePort );
-            }
-            catch ( NumberFormatException ignore )
-            {
-                log.debug( "Remote cache service port property {0}" +
-                    " not specified. An anonymous port will be used.", REMOTE_CACHE_SERVICE_PORT );
-            }
-        }
-
-        String socketTimeoutMillisStr = prop.getProperty( SOCKET_TIMEOUT_MILLIS );
-        if ( socketTimeoutMillisStr != null )
-        {
-            try
-            {
-                int rmiSocketFactoryTimeoutMillis = Integer.parseInt( socketTimeoutMillisStr );
-                rcsa.setRmiSocketFactoryTimeoutMillis( rmiSocketFactoryTimeoutMillis );
-                log.debug( "Remote cache socket timeout {0} ms.", rmiSocketFactoryTimeoutMillis );
-            }
-            catch ( NumberFormatException ignore )
-            {
-                log.debug( "Remote cache socket timeout property {0}" +
-                    " not specified. The default will be used.", SOCKET_TIMEOUT_MILLIS );
-            }
-        }
-
-        String lccStr = prop.getProperty( REMOTE_LOCAL_CLUSTER_CONSISTENCY );
-        if ( lccStr != null )
-        {
-            boolean lcc = Boolean.parseBoolean( lccStr );
-            rcsa.setLocalClusterConsistency( lcc );
-        }
-
-        String acgStr = prop.getProperty( REMOTE_ALLOW_CLUSTER_GET );
-        if ( acgStr != null )
-        {
-            boolean acg = Boolean.parseBoolean( lccStr );
-            rcsa.setAllowClusterGet( acg );
-        }
-
-        // Register the RemoteCacheServer remote object in the registry.
-        rcsa.setRemoteServiceName(
-                prop.getProperty( REMOTE_CACHE_SERVICE_NAME, REMOTE_CACHE_SERVICE_VAL ).trim() );
-    }
-
-    /**
-     * Unbinds the remote server.
-     * <p>
-     * @param host
-     * @param port
-     * @throws IOException
-     */
-    static void shutdownImpl( String host, int port )
-        throws IOException
-    {
-        synchronized ( RemoteCacheServer.class )
-        {
-            if ( remoteCacheServer == null )
-            {
-                return;
-            }
-            log.info( "Unbinding host={0}, port={1}, serviceName={2}",
-                    host, port, getServiceName() );
-            try
-            {
-                Naming.unbind( RemoteUtils.getNamingURL(host, port, getServiceName()) );
-            }
-            catch ( MalformedURLException ex )
-            {
-                // impossible case.
-                throw new IllegalArgumentException( ex.getMessage() + "; host=" + host + ", port=" + port
-                    + ", serviceName=" + getServiceName() );
-            }
-            catch ( NotBoundException ex )
-            {
-                // ignore.
-            }
-            remoteCacheServer.release();
-            remoteCacheServer = null;
-
-            // Shut down keepalive scheduler
-            if ( keepAliveDaemon != null )
-            {
-                keepAliveDaemon.shutdownNow();
-                keepAliveDaemon = null;
-            }
-
-            // Try to release registry
-            if (registry != null)
-            {
-            	UnicastRemoteObject.unexportObject(registry, true);
-            	registry = null;
-            }
-        }
-    }
-
-    /**
-     * Creates an local RMI registry on the default port, starts up the remote cache server, and
-     * binds it to the registry.
-     * <p>
-     * A remote cache is either a local cache or a cluster cache.
-     * <p>
-     * @param args The command line arguments
-     * @throws Exception
-     */
-    public static void main( String[] args )
-        throws Exception
-    {
-        Properties prop = args.length > 0 ? RemoteUtils.loadProps( args[args.length - 1] ) : new Properties();
-
-        int port;
-        try
-        {
-            port = Integer.parseInt( prop.getProperty( "registry.port" ) );
-        }
-        catch ( NumberFormatException ex )
-        {
-            port = Registry.REGISTRY_PORT;
-        }
-
-        // shutdown
-        if ( args.length > 0 && args[0].toLowerCase().indexOf( "-shutdown" ) != -1 )
-        {
-            try
-            {
-                ICacheServiceAdmin admin = lookupCacheServiceAdmin(prop, port);
-                admin.shutdown();
-            }
-            catch ( Exception ex )
-            {
-                log.error( "Problem calling shutdown.", ex );
-            }
-            log.debug( "done." );
-            System.exit( 0 );
-        }
-
-        // STATS
-        if ( args.length > 0 && args[0].toLowerCase().indexOf( "-stats" ) != -1 )
-        {
-            log.debug( "getting cache stats" );
-
-            try
-            {
-                ICacheServiceAdmin admin = lookupCacheServiceAdmin(prop, port);
-
-                try
-                {
-//                    System.out.println( admin.getStats().toString() );
-                    log.debug( admin.getStats() );
-                }
-                catch ( IOException es )
-                {
-                    log.error( es );
-                }
-            }
-            catch ( Exception ex )
-            {
-                log.error( "Problem getting stats.", ex );
-            }
-            log.debug( "done." );
-            System.exit( 0 );
-        }
-
-        // startup.
-        String host = prop.getProperty( "registry.host" );
-
-        if ( host == null || host.trim().equals( "" ) || host.trim().equals( "localhost" ) )
-        {
-            log.debug( "main> creating registry on the localhost" );
-            RemoteUtils.createRegistry( port );
-        }
-        log.debug( "main> starting up RemoteCacheServer" );
-        startup( host, port, prop);
-        log.debug( "main> done" );
-    }
-
-    /**
-     * Look up the remote cache service admin instance
-     *
-     * @param config the configuration properties
-     * @param port the local port
-     * @return the admin object instance
-     *
-     * @throws Exception if lookup fails
-     */
-    private static ICacheServiceAdmin lookupCacheServiceAdmin(Properties config, int port) throws Exception
-    {
-        String remoteServiceName = config.getProperty( REMOTE_CACHE_SERVICE_NAME, REMOTE_CACHE_SERVICE_VAL ).trim();
-        String registry = RemoteUtils.getNamingURL("", port, remoteServiceName);
-
-        log.debug( "looking up server {0}", registry );
-        Object obj = Naming.lookup( registry );
-        log.debug( "server found" );
-
-        return (ICacheServiceAdmin) obj;
-    }
-
-    /**
-     * @param serviceName the serviceName to set
-     */
-    protected static void setServiceName( String serviceName )
-    {
-        RemoteCacheServerFactory.serviceName = serviceName;
-    }
-
-    /**
-     * @return the serviceName
-     */
-    protected static String getServiceName()
-    {
-        return serviceName;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/server/RemoteCacheStartupServlet.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/server/RemoteCacheStartupServlet.java
deleted file mode 100644
index 5bc7431..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/server/RemoteCacheStartupServlet.java
+++ /dev/null
@@ -1,283 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.server;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.net.UnknownHostException;
-import java.nio.charset.StandardCharsets;
-import java.util.Properties;
-
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.apache.commons.jcs.access.exception.CacheException;
-import org.apache.commons.jcs.auxiliary.remote.RemoteUtils;
-import org.apache.commons.jcs.engine.control.CompositeCacheManager;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-import org.apache.commons.jcs.utils.net.HostNameUtil;
-
-/**
- * This servlet can be used to startup the JCS remote cache. It is easy to
- * deploy the remote server in a tomcat base. This give you an easy way to
- * monitor its activity.
- * <p>
- * <code>
- *  servlet&gt;
-        &lt;servlet-name&gt;JCSRemoteCacheStartupServlet&lt;/servlet-name&gt;
-        &lt;servlet-class&gt;
-             org.apache.commons.jcs.auxiliary.remote.server.RemoteCacheStartupServlet
-        &lt;/servlet-class&gt;
-        &lt;load-on-startup&gt;1&lt;/load-on-startup&gt;
-    &lt;/servlet&gt;
-
-
-    &lt;servlet-mapping&gt;
-        &lt;servlet-name&gt;JCSRemoteCacheStartupServlet&lt;/servlet-name&gt;
-        &lt;url-pattern&gt;/jcs&lt;/url-pattern&gt;
-    &lt;/servlet-mapping&gt;
- * </code>
- *
- * @author Aaron Smuts
- */
-public class RemoteCacheStartupServlet
-        extends HttpServlet
-{
-    /** Don't change */
-    private static final long serialVersionUID = 1L;
-
-    /** The logger */
-    private static final Log log = LogManager.getLog(RemoteCacheStartupServlet.class);
-
-    /** The default port to start the registry on. */
-    private static final int DEFAULT_REGISTRY_PORT = 1101;
-
-    /** properties file name */
-    private static final String DEFAULT_PROPS_FILE_NAME = "/cache.ccf";
-
-    /** properties file name, must set prior to calling get instance */
-    private String propsFileName = DEFAULT_PROPS_FILE_NAME;
-
-    /** Configuration properties */
-    private int registryPort = DEFAULT_REGISTRY_PORT;
-
-    /** Configuration properties */
-    private String registryHost = null;
-
-    /**
-     * Starts the registry and then tries to bind to it.
-     * <p>
-     * Gets the port from a props file. Uses the local host name for the
-     * registry host. Tries to start the registry, ignoring failure. Starts the
-     * server.
-     * <p>
-     *
-     * @throws ServletException
-     */
-    @Override
-    public void init()
-            throws ServletException
-    {
-        super.init();
-
-        loadInitParams();
-        Properties props = loadPropertiesFromFile();
-
-        if (registryHost == null)
-        {
-            // we will always use the local machine for the registry
-            try
-            {
-                registryHost = HostNameUtil.getLocalHostAddress();
-            }
-            catch (UnknownHostException e)
-            {
-                log.error("Could not get local address to use for the registry!", e);
-            }
-        }
-
-        log.debug("registryHost = [{0}]", registryHost);
-
-        if ("localhost".equals(registryHost) || "127.0.0.1".equals(registryHost))
-        {
-            log.warn("The local address [{0}] is INVALID. Other machines must "
-                    + "be able to use the address to reach this server.", registryHost);
-        }
-
-        try
-        {
-            if (props == null)
-            {
-                throw new ServletException("Could not load configuration from " + propsFileName);
-            }
-
-            RemoteCacheServerFactory.startup(registryHost, registryPort, props);
-            log.info("Remote JCS Server started with properties from {0}", propsFileName);
-        }
-        catch (IOException e)
-        {
-            throw new ServletException("Problem starting remote cache server.", e);
-        }
-    }
-
-    /**
-     * It just dumps the stats.
-     * <p>
-     *
-     * @param request
-     * @param response
-     * @throws ServletException
-     * @throws IOException
-     */
-    @Override
-    protected void service(HttpServletRequest request, HttpServletResponse response)
-            throws ServletException, IOException
-    {
-        String stats = "";
-
-        try
-        {
-            stats = CompositeCacheManager.getInstance().getStats();
-        }
-        catch (CacheException e)
-        {
-            throw new ServletException(e);
-        }
-
-        log.info(stats);
-
-        try
-        {
-            String characterEncoding = response.getCharacterEncoding();
-            if (characterEncoding == null)
-            {
-                characterEncoding = StandardCharsets.UTF_8.name();
-                response.setCharacterEncoding(characterEncoding);
-            }
-            OutputStream os = response.getOutputStream();
-            os.write(stats.getBytes(characterEncoding));
-            os.close();
-        }
-        catch (IOException e)
-        {
-            log.error("Problem writing response.", e);
-        }
-    }
-
-    /**
-     * shuts the cache down.
-     */
-    @Override
-    public void destroy()
-    {
-        super.destroy();
-
-        log.info("Shutting down remote cache ");
-
-        try
-        {
-            RemoteCacheServerFactory.shutdownImpl(registryHost, registryPort);
-        }
-        catch (IOException e)
-        {
-            log.error("Problem shutting down.", e);
-        }
-
-        try
-        {
-            CompositeCacheManager.getInstance().shutDown();
-        }
-        catch (CacheException e)
-        {
-            log.error("Could not retrieve cache manager instance", e);
-        }
-    }
-
-    /**
-     * Load configuration values from config file if possible
-     */
-    private Properties loadPropertiesFromFile()
-    {
-        Properties props = null;
-
-        try
-        {
-            props = RemoteUtils.loadProps(propsFileName);
-            if (props != null)
-            {
-                registryHost = props.getProperty("registry.host", registryHost);
-                String portS = props.getProperty("registry.port", String.valueOf(registryPort));
-                setRegistryPort(portS);
-            }
-        }
-        catch (IOException e)
-        {
-            log.error("Problem loading props.", e);
-        }
-
-        return props;
-    }
-
-    /**
-     * Load configuration values from init params if possible
-     */
-    private void loadInitParams()
-    {
-        ServletConfig config = getServletConfig();
-        String _propsFileName = config.getInitParameter("propsFileName");
-        if (null != _propsFileName)
-        {
-            this.propsFileName = _propsFileName;
-        }
-        String _registryHost = config.getInitParameter("registryHost");
-        if (null != _registryHost)
-        {
-            this.registryHost = _registryHost;
-        }
-        String regPortString = config.getInitParameter("registryPort");
-        if (null != regPortString)
-        {
-            setRegistryPort(regPortString);
-        }
-    }
-
-    /**
-     * Set registry port from string If the string cannot be parsed, the default
-     * value is used
-     *
-     * @param portS
-     */
-    private void setRegistryPort(String portS)
-    {
-        try
-        {
-            this.registryPort = Integer.parseInt(portS);
-        }
-        catch (NumberFormatException e)
-        {
-            log.error("Problem converting port to an int.", e);
-            this.registryPort = DEFAULT_REGISTRY_PORT;
-        }
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/server/TimeoutConfigurableRMISocketFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/server/TimeoutConfigurableRMISocketFactory.java
deleted file mode 100644
index 7d1f31a..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/server/TimeoutConfigurableRMISocketFactory.java
+++ /dev/null
@@ -1,111 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.server;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.io.Serializable;
-import java.net.InetSocketAddress;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.rmi.server.RMISocketFactory;
-
-/**
- * This can be injected into the the remote cache server as follows:
- *
- * <pre>
- * jcs.remotecache.customrmisocketfactory=org.apache.commons.jcs.auxiliary.remote.server.TimeoutConfigurableRMISocketFactory
- * jcs.remotecache.customrmisocketfactory.readTimeout=5000
- * jcs.remotecache.customrmisocketfactory.openTimeout=5000
- * </pre>
- */
-public class TimeoutConfigurableRMISocketFactory
-    extends RMISocketFactory
-    implements Serializable
-{
-    /** Don't change. */
-    private static final long serialVersionUID = 1489909775271203334L;
-
-    /** The socket read timeout */
-    private int readTimeout = 5000;
-
-    /** The socket open timeout */
-    private int openTimeout = 5000;
-
-    /**
-     * @param port
-     * @return ServerSocket
-     * @throws IOException
-     */
-    @Override
-    public ServerSocket createServerSocket( int port )
-        throws IOException
-    {
-        return new ServerSocket( port );
-    }
-
-    /**
-     * @param host
-     * @param port
-     * @return Socket
-     * @throws IOException
-     */
-    @Override
-    public Socket createSocket( String host, int port )
-        throws IOException
-    {
-        Socket socket = new Socket();
-        socket.setSoTimeout( readTimeout );
-        socket.setSoLinger( false, 0 );
-        socket.connect( new InetSocketAddress( host, port ), openTimeout );
-        return socket;
-    }
-
-    /**
-     * @param readTimeout the readTimeout to set
-     */
-    public void setReadTimeout( int readTimeout )
-    {
-        this.readTimeout = readTimeout;
-    }
-
-    /**
-     * @return the readTimeout
-     */
-    public int getReadTimeout()
-    {
-        return readTimeout;
-    }
-
-    /**
-     * @param openTimeout the openTimeout to set
-     */
-    public void setOpenTimeout( int openTimeout )
-    {
-        this.openTimeout = openTimeout;
-    }
-
-    /**
-     * @return the openTimeout
-     */
-    public int getOpenTimeout()
-    {
-        return openTimeout;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/server/behavior/IRemoteCacheServer.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/server/behavior/IRemoteCacheServer.java
deleted file mode 100644
index 8799b5a..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/server/behavior/IRemoteCacheServer.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.server.behavior;
-
-import java.rmi.Remote;
-import org.apache.commons.jcs.engine.behavior.ICacheObserver;
-import org.apache.commons.jcs.engine.behavior.ICacheServiceAdmin;
-import org.apache.commons.jcs.engine.behavior.ICacheServiceNonLocal;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * Interface for managing Remote objects
- *
- * @author Thomas Vandahl
- *
- */
-public interface IRemoteCacheServer<K, V>
-    extends ICacheServiceNonLocal<K, V>, ICacheObserver, ICacheServiceAdmin, Remote
-{
-    // empty
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/server/behavior/IRemoteCacheServerAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/server/behavior/IRemoteCacheServerAttributes.java
deleted file mode 100644
index 5817e2d..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/server/behavior/IRemoteCacheServerAttributes.java
+++ /dev/null
@@ -1,120 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.server.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.remote.behavior.ICommonRemoteCacheAttributes;
-
-/**
- * This defines the minimal behavior for the objects that are used to configure
- * the remote cache server.
- */
-public interface IRemoteCacheServerAttributes
-    extends ICommonRemoteCacheAttributes
-{
-    /**
-     * Gets the localPort attribute of the IRemoteCacheAttributes object.
-     * <p>
-     * @return The localPort value
-     */
-    int getServicePort();
-
-    /**
-     * Sets the localPort attribute of the IRemoteCacheAttributes object.
-     * <p>
-     * @param p
-     *            The new localPort value
-     */
-    void setServicePort( int p );
-
-    /**
-     * Should we try to get remotely when the request does not come in from a
-     * cluster. If local L1 asks remote server R1 for element A and R1 doesn't
-     * have it, should R1 look remotely? The difference is between a local and a
-     * remote update. The local update stays local. Normal updates, removes,
-     * etc, stay local when they come from a client. If this is set to true,
-     * then they can go remote.
-     * <p>
-     * @return The localClusterConsistency value
-     */
-    boolean isAllowClusterGet();
-
-    /**
-     * Should cluster updates be propagated to the locals.
-     * <p>
-     * @param r
-     *            The new localClusterConsistency value
-     */
-    void setAllowClusterGet( boolean r );
-
-    /**
-     * Gets the ConfigFileName attribute of the IRemoteCacheAttributes object.
-     * <p>
-     * @return The configuration file name
-     */
-    String getConfigFileName();
-
-    /**
-     * Sets the ConfigFileName attribute of the IRemoteCacheAttributes object.
-     * <p>
-     * @param s
-     *            The new configuration file name
-     */
-    void setConfigFileName( String s );
-
-    /**
-     * Should we try to keep the registry alive
-     * <p>
-     * @param useRegistryKeepAlive the useRegistryKeepAlive to set
-     */
-    void setUseRegistryKeepAlive( boolean useRegistryKeepAlive );
-
-    /**
-     * Should we start the registry
-     * <p>
-     * @param startRegistry the startRegistry to set
-     * @deprecated Always true, to be removed
-     */
-    void setStartRegistry( boolean startRegistry );
-
-    /**
-     * Should we start the registry
-     * <p>
-     * @return the startRegistry
-     * @deprecated Always true, to be removed
-     */
-    boolean isStartRegistry();
-
-    /**
-     * Should we try to keep the registry alive
-     * <p>
-     * @return the useRegistryKeepAlive
-     */
-    boolean isUseRegistryKeepAlive();
-
-    /**
-     * @param registryKeepAliveDelayMillis the registryKeepAliveDelayMillis to set
-     */
-    void setRegistryKeepAliveDelayMillis( long registryKeepAliveDelayMillis );
-
-    /**
-     * @return the registryKeepAliveDelayMillis
-     */
-    long getRegistryKeepAliveDelayMillis();
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/server/behavior/RemoteType.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/server/behavior/RemoteType.java
deleted file mode 100644
index 31c7cbf..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/server/behavior/RemoteType.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.server.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * Enum to describe the mode of the remote cache
- */
-public enum RemoteType
-{
-    /** A remote cache is either a local cache or a cluster cache */
-    LOCAL,
-
-    /** A remote cache is either a local cache or a cluster cache */
-    CLUSTER
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/util/RemoteCacheRequestFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/util/RemoteCacheRequestFactory.java
deleted file mode 100644
index b2c1bc8..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/util/RemoteCacheRequestFactory.java
+++ /dev/null
@@ -1,201 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.util;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.Set;
-
-import org.apache.commons.jcs.auxiliary.remote.value.RemoteCacheRequest;
-import org.apache.commons.jcs.auxiliary.remote.value.RemoteRequestType;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * This creates request objects. You could write your own client and use the objects from this
- * factory.
- */
-public class RemoteCacheRequestFactory
-{
-    /** The Logger. */
-    private static final Log log = LogManager.getLog( RemoteCacheRequestFactory.class );
-
-    /**
-     * Create generic request
-     * @param cacheName cache name
-     * @param requestType type of request
-     * @param requesterId id of requester
-     * @return the request
-     */
-    private static <K, V> RemoteCacheRequest<K, V> createRequest(String cacheName, RemoteRequestType requestType, long requesterId)
-    {
-        RemoteCacheRequest<K, V> request = new RemoteCacheRequest<>();
-        request.setCacheName( cacheName );
-        request.setRequestType( requestType );
-        request.setRequesterId( requesterId );
-
-        log.debug( "Created: {0}", request );
-
-        return request;
-    }
-
-    /**
-     * Creates a get Request.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @param requesterId
-     * @return RemoteHttpCacheRequest
-     */
-    public static <K, V> RemoteCacheRequest<K, V> createGetRequest( String cacheName, K key, long requesterId )
-    {
-        RemoteCacheRequest<K, V> request = createRequest(cacheName, RemoteRequestType.GET, requesterId);
-        request.setKey( key );
-
-        return request;
-    }
-
-    /**
-     * Creates a getMatching Request.
-     * <p>
-     * @param cacheName
-     * @param pattern
-     * @param requesterId
-     * @return RemoteHttpCacheRequest
-     */
-    public static <K, V> RemoteCacheRequest<K, V> createGetMatchingRequest( String cacheName, String pattern, long requesterId )
-    {
-        RemoteCacheRequest<K, V> request = createRequest(cacheName, RemoteRequestType.GET_MATCHING, requesterId);
-        request.setPattern( pattern );
-
-        return request;
-    }
-
-    /**
-     * Creates a getMultiple Request.
-     * <p>
-     * @param cacheName
-     * @param keys
-     * @param requesterId
-     * @return RemoteHttpCacheRequest
-     */
-    public static <K, V> RemoteCacheRequest<K, V> createGetMultipleRequest( String cacheName, Set<K> keys, long requesterId )
-    {
-        RemoteCacheRequest<K, V> request = createRequest(cacheName, RemoteRequestType.GET_MULTIPLE, requesterId);
-        request.setKeySet(keys);
-
-        return request;
-    }
-
-    /**
-     * Creates a remove Request.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @param requesterId
-     * @return RemoteHttpCacheRequest
-     */
-    public static <K, V> RemoteCacheRequest<K, V> createRemoveRequest( String cacheName, K key, long requesterId )
-    {
-        RemoteCacheRequest<K, V> request = createRequest(cacheName, RemoteRequestType.REMOVE, requesterId);
-        request.setKey( key );
-
-        return request;
-    }
-
-    /**
-     * Creates a GetKeySet Request.
-     * <p>
-     * @param cacheName
-     * @param requesterId
-     * @return RemoteHttpCacheRequest
-     */
-    public static RemoteCacheRequest<String, String> createGetKeySetRequest( String cacheName, long requesterId )
-    {
-        RemoteCacheRequest<String, String> request = createRequest(cacheName, RemoteRequestType.GET_KEYSET, requesterId);
-        request.setKey( cacheName );
-
-        return request;
-    }
-
-    /**
-     * Creates a removeAll Request.
-     * <p>
-     * @param cacheName
-     * @param requesterId
-     * @return RemoteHttpCacheRequest
-     */
-    public static <K, V> RemoteCacheRequest<K, V> createRemoveAllRequest( String cacheName, long requesterId )
-    {
-        RemoteCacheRequest<K, V> request = createRequest(cacheName, RemoteRequestType.REMOVE_ALL, requesterId);
-
-        return request;
-    }
-
-    /**
-     * Creates a dispose Request.
-     * <p>
-     * @param cacheName
-     * @param requesterId
-     * @return RemoteHttpCacheRequest
-     */
-    public static <K, V> RemoteCacheRequest<K, V> createDisposeRequest( String cacheName, long requesterId )
-    {
-        RemoteCacheRequest<K, V> request = createRequest(cacheName, RemoteRequestType.DISPOSE, requesterId);
-
-        return request;
-    }
-
-    /**
-     * Creates an Update Request.
-     * <p>
-     * @param cacheElement
-     * @param requesterId
-     * @return RemoteHttpCacheRequest
-     */
-    public static <K, V> RemoteCacheRequest<K, V> createUpdateRequest( ICacheElement<K, V> cacheElement, long requesterId )
-    {
-        RemoteCacheRequest<K, V> request = createRequest(null, RemoteRequestType.UPDATE, requesterId);
-        if ( cacheElement != null )
-        {
-            request.setCacheName( cacheElement.getCacheName() );
-            request.setCacheElement( cacheElement );
-            request.setKey( cacheElement.getKey() );
-        }
-        else
-        {
-            log.error( "Can't create a proper update request for a null cache element." );
-        }
-
-        return request;
-    }
-
-    /**
-     * Creates an alive check Request.
-     * <p>
-     * @param requesterId
-     * @return RemoteHttpCacheRequest
-     */
-    public static <K, V> RemoteCacheRequest<K, V> createAliveCheckRequest( long requesterId )
-    {
-        RemoteCacheRequest<K, V> request = createRequest(null, RemoteRequestType.ALIVE_CHECK, requesterId);
-
-        return request;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/value/RemoteCacheRequest.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/value/RemoteCacheRequest.java
deleted file mode 100644
index b99ef2d..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/value/RemoteCacheRequest.java
+++ /dev/null
@@ -1,187 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.value;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-
-import java.io.Serializable;
-import java.util.Set;
-
-/**
- * The basic request wrapper. The different types of requests are differentiated by their types.
- * <p>
- * Rather than creating sub object types, I created on object that has values for all types of
- * requests.
- */
-public class RemoteCacheRequest<K, V>
-    implements Serializable
-{
-    /** Don't change. */
-    private static final long serialVersionUID = -8858447417390442569L;
-
-    /** The request type specifies the type of request: get, put, remove, . . */
-    private RemoteRequestType requestType = null;
-
-    /** Used to identify the source. Same as listener id on the client side. */
-    private long requesterId = 0;
-
-    /** The name of the region */
-    private String cacheName;
-
-    /** The key, if this request has a key. */
-    private K key;
-
-    /** The keySet, if this request has a keySet. Only getMultiple requests. */
-    private Set<K> keySet;
-
-    /** The pattern, if this request uses a pattern. Only getMatching requests. */
-    private String pattern;
-
-    /** The ICacheEleemnt, if this request contains a value. Only update requests will have this. */
-    private ICacheElement<K, V> cacheElement;
-
-    /**
-     * @param requestType the requestType to set
-     */
-    public void setRequestType( RemoteRequestType requestType )
-    {
-        this.requestType = requestType;
-    }
-
-    /**
-     * @return the requestType
-     */
-    public RemoteRequestType getRequestType()
-    {
-        return requestType;
-    }
-
-    /**
-     * @param cacheName the cacheName to set
-     */
-    public void setCacheName( String cacheName )
-    {
-        this.cacheName = cacheName;
-    }
-
-    /**
-     * @return the cacheName
-     */
-    public String getCacheName()
-    {
-        return cacheName;
-    }
-
-    /**
-     * @param key the key to set
-     */
-    public void setKey( K key )
-    {
-        this.key = key;
-    }
-
-    /**
-     * @return the key
-     */
-    public K getKey()
-    {
-        return key;
-    }
-
-    /**
-     * @param pattern the pattern to set
-     */
-    public void setPattern( String pattern )
-    {
-        this.pattern = pattern;
-    }
-
-    /**
-     * @return the pattern
-     */
-    public String getPattern()
-    {
-        return pattern;
-    }
-
-    /**
-     * @param cacheElement the cacheElement to set
-     */
-    public void setCacheElement( ICacheElement<K, V> cacheElement )
-    {
-        this.cacheElement = cacheElement;
-    }
-
-    /**
-     * @return the cacheElement
-     */
-    public ICacheElement<K, V> getCacheElement()
-    {
-        return cacheElement;
-    }
-
-    /**
-     * @param requesterId the requesterId to set
-     */
-    public void setRequesterId( long requesterId )
-    {
-        this.requesterId = requesterId;
-    }
-
-    /**
-     * @return the requesterId
-     */
-    public long getRequesterId()
-    {
-        return requesterId;
-    }
-
-    /**
-     * @param keySet the keySet to set
-     */
-    public void setKeySet( Set<K> keySet )
-    {
-        this.keySet = keySet;
-    }
-
-    /**
-     * @return the keySet
-     */
-    public Set<K> getKeySet()
-    {
-        return keySet;
-    }
-
-    /** @return string */
-    @Override
-    public String toString()
-    {
-        StringBuilder buf = new StringBuilder();
-        buf.append( "\nRemoteHttpCacheRequest" );
-        buf.append( "\n requesterId [" + getRequesterId() + "]" );
-        buf.append( "\n requestType [" + getRequestType() + "]" );
-        buf.append( "\n cacheName [" + getCacheName() + "]" );
-        buf.append( "\n key [" + getKey() + "]" );
-        buf.append( "\n keySet [" + getKeySet() + "]" );
-        buf.append( "\n pattern [" + getPattern() + "]" );
-        buf.append( "\n cacheElement [" + getCacheElement() + "]" );
-        return buf.toString();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/value/RemoteCacheResponse.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/value/RemoteCacheResponse.java
deleted file mode 100644
index 8292a6a..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/value/RemoteCacheResponse.java
+++ /dev/null
@@ -1,105 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.value;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.Serializable;
-
-/**
- * This is the response wrapper. The servlet wraps all different type of responses in one of these
- * objects.
- */
-public class RemoteCacheResponse<T>
-    implements Serializable
-{
-    /** Don't change. */
-    private static final long serialVersionUID = -8858447417390442568L;
-
-    /** Was the event processed without error */
-    private boolean success = true;
-
-    /** Simple error messaging */
-    private String errorMessage;
-
-    /**
-     * The payload. Typically a key / ICacheElement&lt;K, V&gt; map. A normal get will return a map with one
-     * record.
-     */
-    private T payload;
-
-    /**
-     * @param success the success to set
-     */
-    public void setSuccess( boolean success )
-    {
-        this.success = success;
-    }
-
-    /**
-     * @return the success
-     */
-    public boolean isSuccess()
-    {
-        return success;
-    }
-
-    /**
-     * @param errorMessage the errorMessage to set
-     */
-    public void setErrorMessage( String errorMessage )
-    {
-        this.errorMessage = errorMessage;
-    }
-
-    /**
-     * @return the errorMessage
-     */
-    public String getErrorMessage()
-    {
-        return errorMessage;
-    }
-
-    /**
-     * @param payload the payload to set
-     */
-    public void setPayload( T payload )
-    {
-        this.payload = payload;
-    }
-
-    /**
-     * @return the payload
-     */
-    public T getPayload()
-    {
-        return payload;
-    }
-
-    /** @return string */
-    @Override
-    public String toString()
-    {
-        StringBuilder buf = new StringBuilder();
-        buf.append( "\nRemoteHttpCacheResponse" );
-        buf.append( "\n success [" + isSuccess() + "]" );
-        buf.append( "\n payload [" + getPayload() + "]" );
-        buf.append( "\n errorMessage [" + getErrorMessage() + "]" );
-        return buf.toString();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/value/RemoteRequestType.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/value/RemoteRequestType.java
deleted file mode 100644
index ea6d4e1..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/value/RemoteRequestType.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.value;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * The different types of requests
- */
-public enum RemoteRequestType
-{
-    /** Alive check request type. */
-    ALIVE_CHECK,
-
-    /** Get request type. */
-    GET,
-
-    /** Get Multiple request type. */
-    GET_MULTIPLE,
-
-    /** Get Matching request type. */
-    GET_MATCHING,
-
-    /** Update request type. */
-    UPDATE,
-
-    /** Remove request type. */
-    REMOVE,
-
-    /** Remove All request type. */
-    REMOVE_ALL,
-
-    /** Get keys request type. */
-    GET_KEYSET,
-
-    /** Dispose request type. */
-    DISPOSE,
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/AbstractCacheEventQueue.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/AbstractCacheEventQueue.java
deleted file mode 100644
index 18588a5..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/AbstractCacheEventQueue.java
+++ /dev/null
@@ -1,437 +0,0 @@
-package org.apache.commons.jcs.engine;
-
-import java.io.IOException;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheEventQueue;
-import org.apache.commons.jcs.engine.behavior.ICacheListener;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * An abstract base class to the different implementations
- */
-public abstract class AbstractCacheEventQueue<K, V>
-    implements ICacheEventQueue<K, V>
-{
-    /** The logger. */
-    private static final Log log = LogManager.getLog( AbstractCacheEventQueue.class );
-
-    /** default */
-    protected static final int DEFAULT_WAIT_TO_DIE_MILLIS = 10000;
-
-    /**
-     * time to wait for an event before snuffing the background thread if the queue is empty. make
-     * configurable later
-     */
-    private int waitToDieMillis = DEFAULT_WAIT_TO_DIE_MILLIS;
-
-    /**
-     * When the events are pulled off the queue, then tell the listener to handle the specific event
-     * type. The work is done by the listener.
-     */
-    private ICacheListener<K, V> listener;
-
-    /** Id of the listener registered with this queue */
-    private long listenerId;
-
-    /** The cache region name, if applicable. */
-    private String cacheName;
-
-    /** Maximum number of failures before we buy the farm. */
-    private int maxFailure;
-
-    /** in milliseconds */
-    private int waitBeforeRetry;
-
-    /**
-     * This means that the queue is functional. If we reached the max number of failures, the queue
-     * is marked as non functional and will never work again.
-     */
-    private final AtomicBoolean working = new AtomicBoolean(true);
-
-    /**
-     * Returns the time to wait for events before killing the background thread.
-     * <p>
-     * @return int
-     */
-    public int getWaitToDieMillis()
-    {
-        return waitToDieMillis;
-    }
-
-    /**
-     * Sets the time to wait for events before killing the background thread.
-     * <p>
-     * @param wtdm the ms for the q to sit idle.
-     */
-    public void setWaitToDieMillis( int wtdm )
-    {
-        waitToDieMillis = wtdm;
-    }
-
-    /**
-     * Creates a brief string identifying the listener and the region.
-     * <p>
-     * @return String debugging info.
-     */
-    @Override
-    public String toString()
-    {
-        return "CacheEventQueue [listenerId=" + listenerId + ", cacheName=" + cacheName + "]";
-    }
-
-    /**
-     * @return The listenerId value
-     */
-    @Override
-    public long getListenerId()
-    {
-        return listenerId;
-    }
-
-    /**
-     * @return the cacheName
-     */
-    protected String getCacheName()
-    {
-        return cacheName;
-    }
-
-    /**
-     * Initializes the queue.
-     * <p>
-     * @param listener
-     * @param listenerId
-     * @param cacheName
-     * @param maxFailure
-     * @param waitBeforeRetry
-     */
-    protected void initialize( ICacheListener<K, V> listener, long listenerId, String cacheName, int maxFailure,
-                            int waitBeforeRetry)
-    {
-        if ( listener == null )
-        {
-            throw new IllegalArgumentException( "listener must not be null" );
-        }
-
-        this.listener = listener;
-        this.listenerId = listenerId;
-        this.cacheName = cacheName;
-        this.maxFailure = maxFailure <= 0 ? 3 : maxFailure;
-        this.waitBeforeRetry = waitBeforeRetry <= 0 ? 500 : waitBeforeRetry;
-
-        log.debug( "Constructed: {0}", this );
-    }
-
-    /**
-     * This adds a put event to the queue. When it is processed, the element will be put to the
-     * listener.
-     * <p>
-     * @param ce The feature to be added to the PutEvent attribute
-     * @throws IOException
-     */
-    @Override
-    public void addPutEvent( ICacheElement<K, V> ce )
-    {
-        put( new PutEvent( ce ) );
-    }
-
-    /**
-     * This adds a remove event to the queue. When processed the listener's remove method will be
-     * called for the key.
-     * <p>
-     * @param key The feature to be added to the RemoveEvent attribute
-     * @throws IOException
-     */
-    @Override
-    public void addRemoveEvent( K key )
-    {
-        put( new RemoveEvent( key ) );
-    }
-
-    /**
-     * This adds a remove all event to the queue. When it is processed, all elements will be removed
-     * from the cache.
-     */
-    @Override
-    public void addRemoveAllEvent()
-    {
-        put( new RemoveAllEvent() );
-    }
-
-    /**
-     * This adds a dispose event to the queue. When it is processed, the cache is shut down
-     */
-    @Override
-    public void addDisposeEvent()
-    {
-        put( new DisposeEvent() );
-    }
-
-    /**
-     * Adds an event to the queue.
-     * <p>
-     * @param event
-     */
-    protected abstract void put( AbstractCacheEvent event );
-
-
-    // /////////////////////////// Inner classes /////////////////////////////
-    /**
-     * Retries before declaring failure.
-     * <p>
-     * @author asmuts
-     */
-    protected abstract class AbstractCacheEvent implements Runnable
-    {
-        /** Number of failures encountered processing this event. */
-        int failures = 0;
-
-        /**
-         * Main processing method for the AbstractCacheEvent object
-         */
-        @Override
-        @SuppressWarnings("synthetic-access")
-        public void run()
-        {
-            try
-            {
-                doRun();
-            }
-            catch ( IOException e )
-            {
-                log.warn( e );
-                if ( ++failures >= maxFailure )
-                {
-                    log.warn( "Error while running event from Queue: {0}. "
-                            + "Dropping Event and marking Event Queue as "
-                            + "non-functional.", this );
-                    destroy();
-                    return;
-                }
-                log.info( "Error while running event from Queue: {0}. "
-                        + "Retrying...", this );
-                try
-                {
-                    Thread.sleep( waitBeforeRetry );
-                    run();
-                }
-                catch ( InterruptedException ie )
-                {
-                    log.warn( "Interrupted while sleeping for retry on event "
-                            + "{0}.", this );
-                    destroy();
-                }
-            }
-        }
-
-        /**
-         * @throws IOException
-         */
-        protected abstract void doRun()
-            throws IOException;
-    }
-
-    /**
-     * An element should be put in the cache.
-     * <p>
-     * @author asmuts
-     */
-    protected class PutEvent
-        extends AbstractCacheEvent
-    {
-        /** The element to put to the listener */
-        private final ICacheElement<K, V> ice;
-
-        /**
-         * Constructor for the PutEvent object.
-         * <p>
-         * @param ice
-         */
-        PutEvent( ICacheElement<K, V> ice )
-        {
-            this.ice = ice;
-        }
-
-        /**
-         * Call put on the listener.
-         * <p>
-         * @throws IOException
-         */
-        @Override
-        protected void doRun()
-            throws IOException
-        {
-            listener.handlePut( ice );
-        }
-
-        /**
-         * For debugging.
-         * <p>
-         * @return Info on the key and value.
-         */
-        @Override
-        public String toString()
-        {
-            return new StringBuilder( "PutEvent for key: " )
-                    .append( ice.getKey() )
-                    .append( " value: " )
-                    .append( ice.getVal() )
-                    .toString();
-        }
-
-    }
-
-    /**
-     * An element should be removed from the cache.
-     * <p>
-     * @author asmuts
-     */
-    protected class RemoveEvent
-        extends AbstractCacheEvent
-    {
-        /** The key to remove from the listener */
-        private final K key;
-
-        /**
-         * Constructor for the RemoveEvent object
-         * <p>
-         * @param key
-         */
-        RemoveEvent( K key )
-        {
-            this.key = key;
-        }
-
-        /**
-         * Call remove on the listener.
-         * <p>
-         * @throws IOException
-         */
-        @Override
-        protected void doRun()
-            throws IOException
-        {
-            listener.handleRemove( cacheName, key );
-        }
-
-        /**
-         * For debugging.
-         * <p>
-         * @return Info on the key to remove.
-         */
-        @Override
-        public String toString()
-        {
-            return new StringBuilder( "RemoveEvent for " )
-                    .append( key )
-                    .toString();
-        }
-
-    }
-
-    /**
-     * All elements should be removed from the cache when this event is processed.
-     * <p>
-     * @author asmuts
-     */
-    protected class RemoveAllEvent
-        extends AbstractCacheEvent
-    {
-        /**
-         * Call removeAll on the listener.
-         * <p>
-         * @throws IOException
-         */
-        @Override
-        protected void doRun()
-            throws IOException
-        {
-            listener.handleRemoveAll( cacheName );
-        }
-
-        /**
-         * For debugging.
-         * <p>
-         * @return The name of the event.
-         */
-        @Override
-        public String toString()
-        {
-            return "RemoveAllEvent";
-        }
-    }
-
-    /**
-     * The cache should be disposed when this event is processed.
-     * <p>
-     * @author asmuts
-     */
-    protected class DisposeEvent
-        extends AbstractCacheEvent
-    {
-        /**
-         * Called when gets to the end of the queue
-         * <p>
-         * @throws IOException
-         */
-        @Override
-        protected void doRun()
-            throws IOException
-        {
-            listener.handleDispose( cacheName );
-        }
-
-        /**
-         * For debugging.
-         * <p>
-         * @return The name of the event.
-         */
-        @Override
-        public String toString()
-        {
-            return "DisposeEvent";
-        }
-    }
-
-    /**
-     * @return whether the queue is functional.
-     */
-    @Override
-    public boolean isWorking()
-    {
-        return working.get();
-    }
-
-    /**
-     * This means that the queue is functional. If we reached the max number of failures, the queue
-     * is marked as non functional and will never work again.
-     * <p>
-     * @param b
-     */
-    public void setWorking( boolean b )
-    {
-        working.set(b);
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CacheAdaptor.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CacheAdaptor.java
deleted file mode 100644
index 28ea819..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CacheAdaptor.java
+++ /dev/null
@@ -1,137 +0,0 @@
-package org.apache.commons.jcs.engine;
-
-import java.io.IOException;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.behavior.ICache;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheListener;
-
-/**
- * Used for Cache-to-Cache messaging purposes. These are used in the balking
- * facades in the lateral and remote caches.
- */
-public class CacheAdaptor<K, V>
-    implements ICacheListener<K, V>
-{
-    /** The cache we are adapting. */
-    private final ICache<K, V> cache;
-
-    /** The unique id of this listener. */
-    private long listenerId = 0;
-
-    /**
-     * Sets the listenerId attribute of the CacheAdaptor object
-     * <p>
-     * @param id
-     *            The new listenerId value
-     * @throws IOException
-     */
-    @Override
-    public void setListenerId( long id )
-        throws IOException
-    {
-        this.listenerId = id;
-    }
-
-    /**
-     * Gets the listenerId attribute of the CacheAdaptor object
-     * <p>
-     * @return The listenerId value
-     * @throws IOException
-     */
-    @Override
-    public long getListenerId()
-        throws IOException
-    {
-        return this.listenerId;
-    }
-
-    /**
-     * Constructor for the CacheAdaptor object
-     * <p>
-     * @param cache
-     */
-    public CacheAdaptor( ICache<K, V> cache )
-    {
-        this.cache = cache;
-    }
-
-    /**
-     * Puts an item into the cache.
-     * <p>
-     * @param item
-     * @throws IOException
-     */
-    @Override
-    public void handlePut( ICacheElement<K, V> item )
-        throws IOException
-    {
-        try
-        {
-            cache.update( item );
-        }
-        catch ( IOException e )
-        {
-            // swallow
-        }
-    }
-
-    /**
-     * Removes an item.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @throws IOException
-     */
-    @Override
-    public void handleRemove( String cacheName, K key )
-        throws IOException
-    {
-        cache.remove( key );
-    }
-
-    /**
-     * Clears the region.
-     * <p>
-     * @param cacheName
-     * @throws IOException
-     */
-    @Override
-    public void handleRemoveAll( String cacheName )
-        throws IOException
-    {
-        cache.removeAll();
-    }
-
-    /**
-     * Shutdown call.
-     * <p>
-     * @param cacheName
-     * @throws IOException
-     */
-    @Override
-    public void handleDispose( String cacheName )
-        throws IOException
-    {
-        cache.dispose();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CacheElement.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CacheElement.java
deleted file mode 100644
index 224b34d..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CacheElement.java
+++ /dev/null
@@ -1,160 +0,0 @@
-package org.apache.commons.jcs.engine;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-
-/**
- * Generic element wrapper. Often stuffed inside another.
- */
-public class CacheElement<K, V>
-    implements ICacheElement<K, V>
-{
-    /** Don't change */
-    private static final long serialVersionUID = -6062305728297627263L;
-
-    /** The name of the cache region. This is a namespace. */
-    private final String cacheName;
-
-    /** This is the cache key by which the value can be referenced. */
-    private final K key;
-
-    /** This is the cached value, reference by the key. */
-    private final V val;
-
-    /**
-     * These attributes hold information about the element and what it is
-     * allowed to do.
-     */
-    private IElementAttributes attr;
-
-    /**
-     * Constructor for the CacheElement object
-     * <p>
-     * @param cacheName
-     * @param key
-     * @param val
-     */
-    public CacheElement( String cacheName, K key, V val )
-    {
-        this.cacheName = cacheName;
-        this.key = key;
-        this.val = val;
-    }
-
-    /**
-     * Constructor for the CacheElement object
-     * <p>
-     * @param cacheName
-     * @param key
-     * @param val
-     * @param attrArg
-     */
-    public CacheElement( String cacheName, K key, V val, IElementAttributes attrArg )
-    {
-        this(cacheName, key, val);
-        this.attr = attrArg;
-    }
-
-    /**
-     * Gets the cacheName attribute of the CacheElement object
-     * <p>
-     * @return The cacheName value
-     */
-    @Override
-    public String getCacheName()
-    {
-        return this.cacheName;
-    }
-
-    /**
-     * Gets the key attribute of the CacheElement object
-     * <p>
-     * @return The key value
-     */
-    @Override
-    public K getKey()
-    {
-        return this.key;
-    }
-
-    /**
-     * Gets the val attribute of the CacheElement object
-     * <p>
-     * @return The val value
-     */
-    @Override
-    public V getVal()
-    {
-        return this.val;
-    }
-
-    /**
-     * Sets the attributes attribute of the CacheElement object
-     * <p>
-     * @param attr
-     *            The new IElementAttributes value
-     */
-    @Override
-    public void setElementAttributes( IElementAttributes attr )
-    {
-        this.attr = attr;
-    }
-
-    /**
-     * Gets the IElementAttributes attribute of the CacheElement object
-     * <p>
-     * @return The IElementAttributes value, never null
-     */
-    @Override
-    public IElementAttributes getElementAttributes()
-    {
-        // create default attributes if they are null
-        // this shouldn't happen, but could if a corrupt
-        // object was sent over the wire.
-        if ( this.attr == null )
-        {
-            this.attr = new ElementAttributes();
-        }
-        return this.attr;
-    }
-
-    /**
-     * @return a hash of the key only
-     */
-    @Override
-    public int hashCode()
-    {
-        return key.hashCode();
-    }
-
-    /**
-     * For debugging only.
-     * <p>
-     * @return String representation
-     */
-    @Override
-    public String toString()
-    {
-        return "[CacheElement: cacheName [" + cacheName + "], key [" + key + "], val [" + val + "], attr [" + attr
-            + "]";
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CacheElementSerialized.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CacheElementSerialized.java
deleted file mode 100644
index 8a3d253..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CacheElementSerialized.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package org.apache.commons.jcs.engine;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.behavior.ICacheElementSerialized;
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-
-import java.util.Arrays;
-
-/** Either serialized value or the value should be null; */
-public class CacheElementSerialized<K, V>
-    extends CacheElement<K, V>
-    implements ICacheElementSerialized<K, V>
-{
-    /** Don't change. */
-    private static final long serialVersionUID = -7265084818647601874L;
-
-    /** The serialized value. */
-    private final byte[] serializedValue;
-
-    /**
-     * Constructs a usable wrapper.
-     * <p>
-     * @param cacheNameArg
-     * @param keyArg
-     * @param serializedValueArg
-     * @param elementAttributesArg
-     */
-    public CacheElementSerialized( String cacheNameArg, K keyArg, byte[] serializedValueArg,
-                                   IElementAttributes elementAttributesArg )
-    {
-        super(cacheNameArg, keyArg, null, elementAttributesArg);
-        this.serializedValue = serializedValueArg;
-    }
-
-    /** @return byte[] */
-    @Override
-    public byte[] getSerializedValue()
-    {
-        return this.serializedValue;
-    }
-
-    /**
-     * For debugging only.
-     * <p>
-     * @return debugging string.
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder buf = new StringBuilder();
-        buf.append( "\n CacheElementSerialized: " );
-        buf.append( "\n CacheName = [" + getCacheName() + "]" );
-        buf.append( "\n Key = [" + getKey() + "]" );
-        buf.append( "\n SerializedValue = " + Arrays.toString(getSerializedValue()) );
-        buf.append( "\n ElementAttributes = " + getElementAttributes() );
-        return buf.toString();
-    }
-
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CacheEventQueue.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CacheEventQueue.java
deleted file mode 100644
index bf2758f..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CacheEventQueue.java
+++ /dev/null
@@ -1,95 +0,0 @@
-package org.apache.commons.jcs.engine;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.behavior.ICacheListener;
-import org.apache.commons.jcs.utils.threadpool.PoolConfiguration;
-import org.apache.commons.jcs.utils.threadpool.PoolConfiguration.WhenBlockedPolicy;
-import org.apache.commons.jcs.utils.threadpool.ThreadPoolManager;
-
-/**
- * An event queue is used to propagate ordered cache events to one and only one target listener.
- */
-public class CacheEventQueue<K, V>
-    extends PooledCacheEventQueue<K, V>
-{
-    /** The type of queue -- there are pooled and single */
-    private static final QueueType queueType = QueueType.SINGLE;
-
-    /**
-     * Constructs with the specified listener and the cache name.
-     * <p>
-     * @param listener
-     * @param listenerId
-     * @param cacheName
-     */
-    public CacheEventQueue( ICacheListener<K, V> listener, long listenerId, String cacheName )
-    {
-        this( listener, listenerId, cacheName, 10, 500 );
-    }
-
-    /**
-     * Constructor for the CacheEventQueue object
-     * <p>
-     * @param listener
-     * @param listenerId
-     * @param cacheName
-     * @param maxFailure
-     * @param waitBeforeRetry
-     */
-    public CacheEventQueue( ICacheListener<K, V> listener, long listenerId, String cacheName, int maxFailure,
-                            int waitBeforeRetry )
-    {
-        super( listener, listenerId, cacheName, maxFailure, waitBeforeRetry, null );
-    }
-
-    /**
-     * Initializes the queue.
-     * <p>
-     * @param listener
-     * @param listenerId
-     * @param cacheName
-     * @param maxFailure
-     * @param waitBeforeRetry
-     * @param threadPoolName
-     */
-    @Override
-    protected void initialize( ICacheListener<K, V> listener, long listenerId, String cacheName, int maxFailure,
-                            int waitBeforeRetry, String threadPoolName )
-    {
-        super.initialize(listener, listenerId, cacheName, maxFailure, waitBeforeRetry);
-
-        // create a default pool with one worker thread to mimic the SINGLE queue behavior
-        pool = ThreadPoolManager.getInstance().createPool(
-        		new PoolConfiguration(false, 0, 1, 0, getWaitToDieMillis(), WhenBlockedPolicy.RUN, 0),
-        		"CacheEventQueue.QProcessor-" + getCacheName());
-    }
-
-    /**
-     * What type of queue is this.
-     * <p>
-     * @return queueType
-     */
-    @Override
-    public QueueType getQueueType()
-    {
-        return queueType;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CacheEventQueueFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CacheEventQueueFactory.java
deleted file mode 100644
index afeb798..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CacheEventQueueFactory.java
+++ /dev/null
@@ -1,85 +0,0 @@
-package org.apache.commons.jcs.engine;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.behavior.ICacheEventQueue;
-import org.apache.commons.jcs.engine.behavior.ICacheListener;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * This class hands out event Queues. This allows us to change the implementation more easily. You
- * can confugure the cache to use a custom type.
- * <p>
- * @author aaronsm
- */
-public class CacheEventQueueFactory<K, V>
-{
-    /** The logger. */
-    private static final Log log = LogManager.getLog( CacheEventQueueFactory.class );
-
-    /**
-     * The most commonly used factory method.
-     * <p>
-     * @param listener
-     * @param listenerId
-     * @param cacheName
-     * @param threadPoolName
-     * @param poolType - SINGLE, POOLED
-     * @return ICacheEventQueue
-     */
-    public ICacheEventQueue<K, V> createCacheEventQueue( ICacheListener<K, V> listener, long listenerId, String cacheName,
-                                                   String threadPoolName, ICacheEventQueue.QueueType poolType )
-    {
-        return createCacheEventQueue( listener, listenerId, cacheName, 10, 500, threadPoolName, poolType );
-    }
-
-    /**
-     * Fully configured event queue.
-     * <p>
-     * @param listener
-     * @param listenerId
-     * @param cacheName
-     * @param maxFailure
-     * @param waitBeforeRetry
-     * @param threadPoolName null is OK, if not a pooled event queue this is ignored
-     * @param poolType single or pooled
-     * @return ICacheEventQueue
-     */
-    public ICacheEventQueue<K, V> createCacheEventQueue( ICacheListener<K, V> listener, long listenerId, String cacheName,
-                                                   int maxFailure, int waitBeforeRetry, String threadPoolName,
-                                                   ICacheEventQueue.QueueType poolType )
-    {
-        log.debug( "threadPoolName = [{0}] poolType = {1}", threadPoolName, poolType );
-
-        ICacheEventQueue<K, V> eventQueue = null;
-        if ( poolType == null || ICacheEventQueue.QueueType.SINGLE == poolType )
-        {
-            eventQueue = new CacheEventQueue<>( listener, listenerId, cacheName, maxFailure, waitBeforeRetry );
-        }
-        else if ( ICacheEventQueue.QueueType.POOLED == poolType )
-        {
-            eventQueue = new PooledCacheEventQueue<>( listener, listenerId, cacheName, maxFailure, waitBeforeRetry,
-                                                    threadPoolName );
-        }
-
-        return eventQueue;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CacheGroup.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CacheGroup.java
deleted file mode 100644
index 0da5a6d..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CacheGroup.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package org.apache.commons.jcs.engine;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-
-/**
- * Holder for attributes specific to a group. The grouping functionality is on
- * the way out.
- */
-public class CacheGroup
-{
-    /** Element configuration. */
-    private IElementAttributes attr;
-
-    /** Constructor for the CacheGroup object */
-    public CacheGroup()
-    {
-        super();
-    }
-
-    /**
-     * Sets the attributes attribute of the CacheGroup object
-     * <p>
-     * @param attr
-     *            The new attributes value
-     */
-    public void setElementAttributes( IElementAttributes attr )
-    {
-        this.attr = attr;
-    }
-
-    /**
-     * Gets the attrributes attribute of the CacheGroup object
-     * <p>
-     * @return The attrributes value
-     */
-    public IElementAttributes getElementAttrributes()
-    {
-        return attr;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CacheInfo.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CacheInfo.java
deleted file mode 100644
index 6256d0e..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CacheInfo.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package org.apache.commons.jcs.engine;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.rmi.dgc.VMID;
-
-/**
- * This is a static variable holder for the distribution auxiliaries that need something like a vmid.
- */
-public final class CacheInfo
-{
-    /** shouldn't be instantiated */
-    private CacheInfo()
-    {
-        super();
-    }
-
-    /**
-     * Used to identify a client, so we can run multiple clients off one host.
-     * Need since there is no way to identify a client other than by host in
-     * rmi.
-     * <p>
-     * TODO: may have some trouble in failover mode if the cache keeps its old
-     * id. We may need to reset this when moving into failover.
-     */
-    private static final VMID vmid = new VMID();
-
-    /** By default this is the hashcode of the VMID */
-    public static final long listenerId = vmid.hashCode();
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CacheListeners.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CacheListeners.java
deleted file mode 100644
index 1359c4b..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CacheListeners.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package org.apache.commons.jcs.engine;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-import org.apache.commons.jcs.engine.behavior.ICache;
-import org.apache.commons.jcs.engine.behavior.ICacheEventQueue;
-
-/**
- * Used to associates a set of [cache listener to cache event queue] for a
- * cache.
- */
-public class CacheListeners<K, V>
-{
-    /** The cache using the queue. */
-    public final ICache<K, V> cache;
-
-    /** Map ICacheListener to ICacheEventQueue */
-    public final ConcurrentMap<Long, ICacheEventQueue<K, V>> eventQMap =
-        new ConcurrentHashMap<>();
-
-    /**
-     * Constructs with the given cache.
-     * <p>
-     * @param cache
-     */
-    public CacheListeners( ICache<K, V> cache )
-    {
-        if ( cache == null )
-        {
-            throw new IllegalArgumentException( "cache must not be null" );
-        }
-        this.cache = cache;
-    }
-
-    /** @return info on the listeners */
-    @Override
-    public String toString()
-    {
-        StringBuilder buffer = new StringBuilder();
-        buffer.append( "\n CacheListeners" );
-        if ( cache != null )
-        {
-            buffer.append( "\n Region = " + cache.getCacheName() );
-        }
-        if ( eventQMap != null )
-        {
-            buffer.append( "\n Event Queue Map " );
-            buffer.append( "\n size = " + eventQMap.size() );
-            eventQMap.forEach((key, value)
-                    -> buffer.append( "\n Entry: key: ").append(key)
-                        .append(", value: ").append(value));
-        }
-        else
-        {
-            buffer.append( "\n No Listeners. " );
-        }
-        return buffer.toString();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CacheStatus.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CacheStatus.java
deleted file mode 100644
index 6d09baf..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CacheStatus.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package org.apache.commons.jcs.engine;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * Cache statuses
- * <p>
- * @version $Id: CacheStatus.java 536904 2007-05-10 16:03:42Z tv $
- */
-public enum CacheStatus
-{
-    /** Cache alive status. */
-    ALIVE,
-
-    /** Cache disposed status. */
-    DISPOSED,
-
-    /** Cache in error. */
-    ERROR
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CacheWatchRepairable.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CacheWatchRepairable.java
deleted file mode 100644
index 97b5a2b..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CacheWatchRepairable.java
+++ /dev/null
@@ -1,170 +0,0 @@
-package org.apache.commons.jcs.engine;
-
-import java.io.IOException;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.CopyOnWriteArraySet;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.behavior.ICacheListener;
-import org.apache.commons.jcs.engine.behavior.ICacheObserver;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * Intercepts the requests to the underlying ICacheObserver object so that the listeners can be
- * recorded locally for remote connection recovery purposes. (Durable subscription like those in JMS
- * is not implemented at this stage for it can be too expensive.)
- */
-public class CacheWatchRepairable
-    implements ICacheObserver
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog( CacheWatchRepairable.class );
-
-    /** the underlying ICacheObserver. */
-    private ICacheObserver cacheWatch;
-
-    /** Map of cache regions. */
-    private final ConcurrentMap<String, Set<ICacheListener<?, ?>>> cacheMap =
-        new ConcurrentHashMap<>();
-
-    /**
-     * Replaces the underlying cache watch service and re-attaches all existing listeners to the new
-     * cache watch.
-     * <p>
-     * @param cacheWatch The new cacheWatch value
-     */
-    public void setCacheWatch( ICacheObserver cacheWatch )
-    {
-        this.cacheWatch = cacheWatch;
-        for (Map.Entry<String, Set<ICacheListener<?, ?>>> entry : cacheMap.entrySet())
-        {
-            String cacheName = entry.getKey();
-            for (ICacheListener<?, ?> listener : entry.getValue())
-            {
-                try
-                {
-                    log.info( "Adding listener to cache watch. ICacheListener = "
-                            + "{0} | ICacheObserver = {1}", listener, cacheWatch );
-                    cacheWatch.addCacheListener( cacheName, listener );
-                }
-                catch ( IOException ex )
-                {
-                    log.error( "Problem adding listener. ICacheListener = {0} | "
-                            + "ICacheObserver = {1}", listener, cacheWatch, ex );
-                }
-            }
-        }
-    }
-
-    /**
-     * Adds a feature to the CacheListener attribute of the CacheWatchRepairable object
-     * <p>
-     * @param cacheName The feature to be added to the CacheListener attribute
-     * @param obj The feature to be added to the CacheListener attribute
-     * @throws IOException
-     */
-    @Override
-    public <K, V> void addCacheListener( String cacheName, ICacheListener<K, V> obj )
-        throws IOException
-    {
-        // Record the added cache listener locally, regardless of whether the
-        // remote add-listener operation succeeds or fails.
-        Set<ICacheListener<?, ?>> listenerSet = cacheMap.computeIfAbsent(cacheName, key -> {
-            return new CopyOnWriteArraySet<>();
-        });
-
-        listenerSet.add( obj );
-
-        log.info( "Adding listener to cache watch. ICacheListener = {0} | "
-                + "ICacheObserver = {1} | cacheName = {2}", obj, cacheWatch,
-                cacheName );
-        cacheWatch.addCacheListener( cacheName, obj );
-    }
-
-    /**
-     * Adds a feature to the CacheListener attribute of the CacheWatchRepairable object
-     * <p>
-     * @param obj The feature to be added to the CacheListener attribute
-     * @throws IOException
-     */
-    @Override
-    public <K, V> void addCacheListener( ICacheListener<K, V> obj )
-        throws IOException
-    {
-        // Record the added cache listener locally, regardless of whether the
-        // remote add-listener operation succeeds or fails.
-        for (Set<ICacheListener<?, ?>> listenerSet : cacheMap.values())
-        {
-            listenerSet.add( obj );
-        }
-
-        log.info( "Adding listener to cache watch. ICacheListener = {0} | "
-                + "ICacheObserver = {1}", obj, cacheWatch );
-        cacheWatch.addCacheListener( obj );
-    }
-
-    /**
-     * Tell the server to release us.
-     * <p>
-     * @param cacheName
-     * @param obj
-     * @throws IOException
-     */
-    @Override
-    public <K, V> void removeCacheListener( String cacheName, ICacheListener<K, V> obj )
-        throws IOException
-    {
-        log.info( "removeCacheListener, cacheName [{0}]", cacheName );
-        // Record the removal locally, regardless of whether the remote
-        // remove-listener operation succeeds or fails.
-        Set<ICacheListener<?, ?>> listenerSet = cacheMap.get( cacheName );
-        if ( listenerSet != null )
-        {
-            listenerSet.remove( obj );
-        }
-        cacheWatch.removeCacheListener( cacheName, obj );
-    }
-
-    /**
-     * @param obj
-     * @throws IOException
-     */
-    @Override
-    public <K, V> void removeCacheListener( ICacheListener<K, V> obj )
-        throws IOException
-    {
-        log.info( "removeCacheListener, ICacheListener [{0}]", obj );
-
-        // Record the removal locally, regardless of whether the remote
-        // remove-listener operation succeeds or fails.
-        for (Set<ICacheListener<?, ?>> listenerSet : cacheMap.values())
-        {
-            log.debug( "Before removing [{0}] the listenerSet = {1}", obj,
-                    listenerSet );
-            listenerSet.remove( obj );
-        }
-        cacheWatch.removeCacheListener( obj );
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CompositeCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CompositeCacheAttributes.java
deleted file mode 100644
index 31da4be..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/CompositeCacheAttributes.java
+++ /dev/null
@@ -1,443 +0,0 @@
-package org.apache.commons.jcs.engine;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheAttributes;
-
-/**
- * The CompositeCacheAttributes defines the general cache region settings. If a region is not
- * explicitly defined in the cache.ccf then it inherits the cache default settings.
- * <p>
- * If all the default attributes are not defined in the default region definition in the cache.ccf,
- * the hard coded defaults will be used.
- */
-public class CompositeCacheAttributes
-    implements ICompositeCacheAttributes
-{
-    /** Don't change */
-    private static final long serialVersionUID = 6754049978134196787L;
-
-    /** default lateral switch */
-    private static final boolean DEFAULT_USE_LATERAL = true;
-
-    /** default remote switch */
-    private static final boolean DEFAULT_USE_REMOTE = true;
-
-    /** default disk switch */
-    private static final boolean DEFAULT_USE_DISK = true;
-
-    /** default shrinker setting */
-    private static final boolean DEFAULT_USE_SHRINKER = false;
-
-    /** default max objects value */
-    private static final int DEFAULT_MAX_OBJECTS = 100;
-
-    /** default */
-    private static final int DEFAULT_MAX_MEMORY_IDLE_TIME_SECONDS = 60 * 120;
-
-    /** default interval to run the shrinker */
-    private static final int DEFAULT_SHRINKER_INTERVAL_SECONDS = 30;
-
-    /** default */
-    private static final int DEFAULT_MAX_SPOOL_PER_RUN = -1;
-
-    /** default */
-    private static final String DEFAULT_MEMORY_CACHE_NAME = "org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache";
-
-    /** Default number to send to disk at a time when memory fills. */
-    private static final int DEFAULT_CHUNK_SIZE = 2;
-
-    /** allow lateral caches */
-    private boolean useLateral = DEFAULT_USE_LATERAL;
-
-    /** allow remote caches */
-    private boolean useRemote = DEFAULT_USE_REMOTE;
-
-    /** Whether we should use a disk cache if it is configured. */
-    private boolean useDisk = DEFAULT_USE_DISK;
-
-    /** Whether or not we should run the memory shrinker thread. */
-    private boolean useMemoryShrinker = DEFAULT_USE_SHRINKER;
-
-    /** The maximum objects that the memory cache will be allowed to hold. */
-    private int maxObjs = DEFAULT_MAX_OBJECTS;
-
-    /** maxMemoryIdleTimeSeconds */
-    private long maxMemoryIdleTimeSeconds = DEFAULT_MAX_MEMORY_IDLE_TIME_SECONDS;
-
-    /** shrinkerIntervalSeconds */
-    private long shrinkerIntervalSeconds = DEFAULT_SHRINKER_INTERVAL_SECONDS;
-
-    /** The maximum number the shrinker will spool to disk per run. */
-    private int maxSpoolPerRun = DEFAULT_MAX_SPOOL_PER_RUN;
-
-    /** The name of this cache region. */
-    private String cacheName;
-
-    /** The name of the memory cache implementation class. */
-    private String memoryCacheName;
-
-    /** Set via DISK_USAGE_PATTERN_NAME */
-    private DiskUsagePattern diskUsagePattern = DiskUsagePattern.SWAP;
-
-    /** How many to spool to disk at a time. */
-    private int spoolChunkSize = DEFAULT_CHUNK_SIZE;
-
-    /**
-     * Constructor for the CompositeCacheAttributes object
-     */
-    public CompositeCacheAttributes()
-    {
-        super();
-        // set this as the default so the configuration is a bit simpler
-        memoryCacheName = DEFAULT_MEMORY_CACHE_NAME;
-    }
-
-    /**
-     * Sets the maxObjects attribute of the CompositeCacheAttributes object
-     * <p>
-     * @param maxObjs The new maxObjects value
-     */
-    @Override
-    public void setMaxObjects( int maxObjs )
-    {
-        this.maxObjs = maxObjs;
-    }
-
-    /**
-     * Gets the maxObjects attribute of the CompositeCacheAttributes object
-     * <p>
-     * @return The maxObjects value
-     */
-    @Override
-    public int getMaxObjects()
-    {
-        return this.maxObjs;
-    }
-
-    /**
-     * Sets the useDisk attribute of the CompositeCacheAttributes object
-     * <p>
-     * @param useDisk The new useDisk value
-     */
-    @Override
-    public void setUseDisk( boolean useDisk )
-    {
-        this.useDisk = useDisk;
-    }
-
-    /**
-     * Gets the useDisk attribute of the CompositeCacheAttributes object
-     * <p>
-     * @return The useDisk value
-     */
-    @Override
-    public boolean isUseDisk()
-    {
-        return useDisk;
-    }
-
-    /**
-     * Sets the useLateral attribute of the CompositeCacheAttributes object
-     * <p>
-     * @param b The new useLateral value
-     */
-    @Override
-    public void setUseLateral( boolean b )
-    {
-        this.useLateral = b;
-    }
-
-    /**
-     * Gets the useLateral attribute of the CompositeCacheAttributes object
-     * <p>
-     * @return The useLateral value
-     */
-    @Override
-    public boolean isUseLateral()
-    {
-        return this.useLateral;
-    }
-
-    /**
-     * Sets the useRemote attribute of the CompositeCacheAttributes object
-     * <p>
-     * @param useRemote The new useRemote value
-     */
-    @Override
-    public void setUseRemote( boolean useRemote )
-    {
-        this.useRemote = useRemote;
-    }
-
-    /**
-     * Gets the useRemote attribute of the CompositeCacheAttributes object
-     * <p>
-     * @return The useRemote value
-     */
-    @Override
-    public boolean isUseRemote()
-    {
-        return this.useRemote;
-    }
-
-    /**
-     * Sets the cacheName attribute of the CompositeCacheAttributes object
-     * <p>
-     * @param s The new cacheName value
-     */
-    @Override
-    public void setCacheName( String s )
-    {
-        this.cacheName = s;
-    }
-
-    /**
-     * Gets the cacheName attribute of the CompositeCacheAttributes object
-     * <p>
-     * @return The cacheName value
-     */
-    @Override
-    public String getCacheName()
-    {
-        return this.cacheName;
-    }
-
-    /**
-     * Sets the memoryCacheName attribute of the CompositeCacheAttributes object
-     * <p>
-     * @param s The new memoryCacheName value
-     */
-    @Override
-    public void setMemoryCacheName( String s )
-    {
-        this.memoryCacheName = s;
-    }
-
-    /**
-     * Gets the memoryCacheName attribute of the CompositeCacheAttributes object
-     * <p>
-     * @return The memoryCacheName value
-     */
-    @Override
-    public String getMemoryCacheName()
-    {
-        return this.memoryCacheName;
-    }
-
-    /**
-     * Whether the memory cache should perform background memory shrinkage.
-     * <p>
-     * @param useShrinker The new UseMemoryShrinker value
-     */
-    @Override
-    public void setUseMemoryShrinker( boolean useShrinker )
-    {
-        this.useMemoryShrinker = useShrinker;
-    }
-
-    /**
-     * Whether the memory cache should perform background memory shrinkage.
-     * <p>
-     * @return The UseMemoryShrinker value
-     */
-    @Override
-    public boolean isUseMemoryShrinker()
-    {
-        return this.useMemoryShrinker;
-    }
-
-    /**
-     * If UseMemoryShrinker is true the memory cache should auto-expire elements to reclaim space.
-     * <p>
-     * @param seconds The new MaxMemoryIdleTimeSeconds value
-     */
-    @Override
-    public void setMaxMemoryIdleTimeSeconds( long seconds )
-    {
-        this.maxMemoryIdleTimeSeconds = seconds;
-    }
-
-    /**
-     * If UseMemoryShrinker is true the memory cache should auto-expire elements to reclaim space.
-     * <p>
-     * @return The MaxMemoryIdleTimeSeconds value
-     */
-    @Override
-    public long getMaxMemoryIdleTimeSeconds()
-    {
-        return this.maxMemoryIdleTimeSeconds;
-    }
-
-    /**
-     * If UseMemoryShrinker is true the memory cache should auto-expire elements to reclaim space.
-     * This sets the shrinker interval.
-     * <p>
-     * @param seconds The new ShrinkerIntervalSeconds value
-     */
-    @Override
-    public void setShrinkerIntervalSeconds( long seconds )
-    {
-        this.shrinkerIntervalSeconds = seconds;
-    }
-
-    /**
-     * If UseMemoryShrinker is true the memory cache should auto-expire elements to reclaim space.
-     * This gets the shrinker interval.
-     * <p>
-     * @return The ShrinkerIntervalSeconds value
-     */
-    @Override
-    public long getShrinkerIntervalSeconds()
-    {
-        return this.shrinkerIntervalSeconds;
-    }
-
-    /**
-     * If UseMemoryShrinker is true the memory cache should auto-expire elements to reclaim space.
-     * This sets the maximum number of items to spool per run.
-     * <p>
-     * If the value is -1, then there is no limit to the number of items to be spooled.
-     * <p>
-     * @param maxSpoolPerRun The new maxSpoolPerRun value
-     */
-    @Override
-    public void setMaxSpoolPerRun( int maxSpoolPerRun )
-    {
-        this.maxSpoolPerRun = maxSpoolPerRun;
-    }
-
-    /**
-     * If UseMemoryShrinker is true the memory cache should auto-expire elements to reclaim space.
-     * This gets the maximum number of items to spool per run.
-     * <p>
-     * @return The maxSpoolPerRun value
-     */
-    @Override
-    public int getMaxSpoolPerRun()
-    {
-        return this.maxSpoolPerRun;
-    }
-
-    /**
-     * By default this is SWAP_ONLY.
-     * <p>
-     * @param diskUsagePattern The diskUsagePattern to set.
-     */
-    @Override
-    public void setDiskUsagePattern( DiskUsagePattern diskUsagePattern )
-    {
-        this.diskUsagePattern = diskUsagePattern;
-    }
-
-    /**
-     * Translates the name to the disk usage pattern short value.
-     * <p>
-     * The allowed values are SWAP and UPDATE.
-     * <p>
-     * @param diskUsagePatternName The diskUsagePattern to set.
-     */
-    @Override
-    public void setDiskUsagePatternName( String diskUsagePatternName )
-    {
-        if ( diskUsagePatternName != null )
-        {
-            String name = diskUsagePatternName.toUpperCase().trim();
-            if ( name.startsWith( "SWAP" ) )
-            {
-                this.setDiskUsagePattern( DiskUsagePattern.SWAP );
-            }
-            else if ( name.startsWith( "UPDATE" ) )
-            {
-                this.setDiskUsagePattern( DiskUsagePattern.UPDATE );
-            }
-        }
-    }
-
-    /**
-     * Number to send to disk at at time when memory is full.
-     * <p>
-     * @return int
-     */
-    @Override
-    public int getSpoolChunkSize()
-    {
-        return spoolChunkSize;
-    }
-
-    /**
-     * Number to send to disk at a time.
-     * <p>
-     * @param spoolChunkSize
-     */
-    @Override
-    public void setSpoolChunkSize( int spoolChunkSize )
-    {
-        this.spoolChunkSize = spoolChunkSize;
-    }
-
-    /**
-     * @return Returns the diskUsagePattern.
-     */
-    @Override
-    public DiskUsagePattern getDiskUsagePattern()
-    {
-        return diskUsagePattern;
-    }
-
-    /**
-     * Dumps the core attributes.
-     * <p>
-     * @return For debugging.
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder dump = new StringBuilder();
-
-        dump.append( "[ " );
-        dump.append( "useLateral = " ).append( useLateral );
-        dump.append( ", useRemote = " ).append( useRemote );
-        dump.append( ", useDisk = " ).append( useDisk );
-        dump.append( ", maxObjs = " ).append( maxObjs );
-        dump.append( ", maxSpoolPerRun = " ).append( maxSpoolPerRun );
-        dump.append( ", diskUsagePattern = " ).append( diskUsagePattern );
-        dump.append( ", spoolChunkSize = " ).append( spoolChunkSize );
-        dump.append( " ]" );
-
-        return dump.toString();
-    }
-
-    /**
-     * @see java.lang.Object#clone()
-     */
-    @Override
-    public ICompositeCacheAttributes clone()
-    {
-        try
-        {
-            return (ICompositeCacheAttributes)super.clone();
-        }
-        catch (CloneNotSupportedException e)
-        {
-            throw new RuntimeException("Clone not supported. This should never happen.", e);
-        }
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/ElementAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/ElementAttributes.java
deleted file mode 100644
index 053fefc..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/ElementAttributes.java
+++ /dev/null
@@ -1,459 +0,0 @@
-package org.apache.commons.jcs.engine;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-import org.apache.commons.jcs.engine.control.event.behavior.IElementEventHandler;
-
-/**
- * This it the element attribute descriptor class. Each element in the cache has an ElementAttribute
- * object associated with it. An ElementAttributes object can be associated with an element in 3
- * ways:
- * <ol>
- * <li>When the item is put into the cache, you can associate an element attributes object.</li>
- * <li>If not attributes object is include when the element is put into the cache, then the default
- * attributes for the region will be used.</li>
- * <li>The element attributes can be reset. This effectively results in a retrieval followed by a
- * put. Hence, this is the same as 1.</li>
- * </ol>
- */
-public class ElementAttributes
-    implements IElementAttributes
-{
-    /** Don't change. */
-    private static final long serialVersionUID = 7814990748035017441L;
-
-    /** Can this item be flushed to disk */
-    private boolean IS_SPOOL = true;
-
-    /** Is this item laterally distributable */
-    private boolean IS_LATERAL = true;
-
-    /** Can this item be sent to the remote cache */
-    private boolean IS_REMOTE = true;
-
-    /**
-     * You can turn off expiration by setting this to true. This causes the cache to bypass both max
-     * life and idle time expiration.
-     */
-    private boolean IS_ETERNAL = true;
-
-    /** Max life seconds */
-    private long maxLife = -1;
-
-    /**
-     * The maximum time an entry can be idle. Setting this to -1 causes the idle time check to be
-     * ignored.
-     */
-    private long maxIdleTime = -1;
-
-    /** The byte size of the field. Must be manually set. */
-    private int size = 0;
-
-    /** The creation time. This is used to enforce the max life. */
-    private long createTime = 0;
-
-    /** The last access time. This is used to enforce the max idel time. */
-    private long lastAccessTime = 0;
-
-    /**
-     * The list of Event handlers to use. This is transient, since the event handlers cannot usually
-     * be serialized. This means that you cannot attach a post serialization event to an item.
-     * <p>
-     * TODO we need to check that when an item is passed to a non-local cache that if the local
-     * cache had a copy with event handlers, that those handlers are used.
-     */
-    private transient ArrayList<IElementEventHandler> eventHandlers;
-
-    private long timeFactor = 1000;
-
-    /**
-     * Constructor for the IElementAttributes object
-     */
-    public ElementAttributes()
-    {
-        this.createTime = System.currentTimeMillis();
-        this.lastAccessTime = this.createTime;
-    }
-
-    /**
-     * Constructor for the IElementAttributes object
-     * <p>
-     * @param attr
-     */
-    protected ElementAttributes( ElementAttributes attr )
-    {
-        IS_ETERNAL = attr.IS_ETERNAL;
-
-        // waterfall onto disk, for pure disk set memory to 0
-        IS_SPOOL = attr.IS_SPOOL;
-
-        // lateral
-        IS_LATERAL = attr.IS_LATERAL;
-
-        // central rmi store
-        IS_REMOTE = attr.IS_REMOTE;
-
-        maxLife = attr.maxLife;
-        // time-to-live
-        maxIdleTime = attr.maxIdleTime;
-        size = attr.size;
-    }
-
-    /**
-     * Sets the maxLife attribute of the IAttributes object.
-     * <p>
-     * @param mls The new MaxLifeSeconds value
-     */
-    @Override
-    public void setMaxLife(long mls)
-    {
-        this.maxLife = mls;
-    }
-
-    /**
-     * Sets the maxLife attribute of the IAttributes object. How many seconds it can live after
-     * creation.
-     * <p>
-     * If this is exceeded the element will not be returned, instead it will be removed. It will be
-     * removed on retrieval, or removed actively if the memory shrinker is turned on.
-     * @return The MaxLifeSeconds value
-     */
-    @Override
-    public long getMaxLife()
-    {
-        return this.maxLife;
-    }
-
-    /**
-     * Sets the idleTime attribute of the IAttributes object. This is the maximum time the item can
-     * be idle in the cache, that is not accessed.
-     * <p>
-     * If this is exceeded the element will not be returned, instead it will be removed. It will be
-     * removed on retrieval, or removed actively if the memory shrinker is turned on.
-     * @param idle The new idleTime value
-     */
-    @Override
-    public void setIdleTime( long idle )
-    {
-        this.maxIdleTime = idle;
-    }
-
-    /**
-     * Size in bytes. This is not used except in the admin pages. It will be 0 by default
-     * and is only updated when the element is serialized.
-     * <p>
-     * @param size The new size value
-     */
-    @Override
-    public void setSize( int size )
-    {
-        this.size = size;
-    }
-
-    /**
-     * Gets the size attribute of the IAttributes object
-     * <p>
-     * @return The size value
-     */
-    @Override
-    public int getSize()
-    {
-        return size;
-    }
-
-    /**
-     * Gets the createTime attribute of the IAttributes object.
-     * <p>
-     * This should be the current time in milliseconds returned by the sysutem call when the element
-     * is put in the cache.
-     * <p>
-     * Putting an item in the cache overrides any existing items.
-     * @return The createTime value
-     */
-    @Override
-    public long getCreateTime()
-    {
-        return createTime;
-    }
-
-    /**
-     * Sets the createTime attribute of the IElementAttributes object
-     */
-    public void setCreateTime()
-    {
-        createTime = System.currentTimeMillis();
-    }
-
-    /**
-     * Gets the idleTime attribute of the IAttributes object.
-     * <p>
-     * @return The idleTime value
-     */
-    @Override
-    public long getIdleTime()
-    {
-        return this.maxIdleTime;
-    }
-
-    /**
-     * Gets the time left to live of the IAttributes object.
-     * <p>
-     * This is the (max life + create time) - current time.
-     * @return The TimeToLiveSeconds value
-     */
-    @Override
-    public long getTimeToLiveSeconds()
-    {
-        final long now = System.currentTimeMillis();
-        final long timeFactorForMilliseconds = getTimeFactorForMilliseconds();
-        return ( this.getCreateTime() + this.getMaxLife() * timeFactorForMilliseconds - now ) / 1000;
-    }
-
-    /**
-     * Gets the LastAccess attribute of the IAttributes object.
-     * <p>
-     * @return The LastAccess value.
-     */
-    @Override
-    public long getLastAccessTime()
-    {
-        return this.lastAccessTime;
-    }
-
-    /**
-     * Sets the LastAccessTime as now of the IElementAttributes object
-     */
-    @Override
-    public void setLastAccessTimeNow()
-    {
-        this.lastAccessTime = System.currentTimeMillis();
-    }
-
-    /**
-     * only for use from test code
-     */
-    public void setLastAccessTime(long time)
-    {
-        this.lastAccessTime = time;
-    }
-
-    /**
-     * Can this item be spooled to disk
-     * <p>
-     * By default this is true.
-     * @return The spoolable value
-     */
-    @Override
-    public boolean getIsSpool()
-    {
-        return this.IS_SPOOL;
-    }
-
-    /**
-     * Sets the isSpool attribute of the IElementAttributes object
-     * <p>
-     * By default this is true.
-     * @param val The new isSpool value
-     */
-    @Override
-    public void setIsSpool( boolean val )
-    {
-        this.IS_SPOOL = val;
-    }
-
-    /**
-     * Is this item laterally distributable. Can it be sent to auxiliaries of type lateral.
-     * <p>
-     * By default this is true.
-     * @return The isLateral value
-     */
-    @Override
-    public boolean getIsLateral()
-    {
-        return this.IS_LATERAL;
-    }
-
-    /**
-     * Sets the isLateral attribute of the IElementAttributes object
-     * <p>
-     * By default this is true.
-     * @param val The new isLateral value
-     */
-    @Override
-    public void setIsLateral( boolean val )
-    {
-        this.IS_LATERAL = val;
-    }
-
-    /**
-     * Can this item be sent to the remote cache
-     * @return true if the item can be sent to a remote auxiliary
-     */
-    @Override
-    public boolean getIsRemote()
-    {
-        return this.IS_REMOTE;
-    }
-
-    /**
-     * Sets the isRemote attribute of the ElementAttributes object
-     * @param val The new isRemote value
-     */
-    @Override
-    public void setIsRemote( boolean val )
-    {
-        this.IS_REMOTE = val;
-    }
-
-    /**
-     * You can turn off expiration by setting this to true. The max life value will be ignored.
-     * <p>
-     * @return true if the item cannot expire.
-     */
-    @Override
-    public boolean getIsEternal()
-    {
-        return this.IS_ETERNAL;
-    }
-
-    /**
-     * Sets the isEternal attribute of the ElementAttributes object. True means that the item should
-     * never expire. If can still be removed if it is the least recently used, and you are using the
-     * LRUMemory cache. it just will not be filtered for expiration by the cache hub.
-     * <p>
-     * @param val The new isEternal value
-     */
-    @Override
-    public void setIsEternal( boolean val )
-    {
-        this.IS_ETERNAL = val;
-    }
-
-    /**
-     * Adds a ElementEventHandler. Handler's can be registered for multiple events. A registered
-     * handler will be called at every recognized event.
-     * <p>
-     * The alternative would be to register handlers for each event. Or maybe The handler interface
-     * should have a method to return whether it cares about certain events.
-     * <p>
-     * @param eventHandler The ElementEventHandler to be added to the list.
-     */
-    @Override
-    public void addElementEventHandler( IElementEventHandler eventHandler )
-    {
-        // lazy here, no concurrency problems expected
-        if ( this.eventHandlers == null )
-        {
-            this.eventHandlers = new ArrayList<>();
-        }
-        this.eventHandlers.add( eventHandler );
-    }
-
-    /**
-     * Sets the eventHandlers of the IElementAttributes object.
-     * <p>
-     * This add the references to the local list. Subsequent changes in the caller's list will not
-     * be reflected.
-     * <p>
-     * @param eventHandlers List of IElementEventHandler objects
-     */
-    @Override
-    public void addElementEventHandlers( List<IElementEventHandler> eventHandlers )
-    {
-        if ( eventHandlers == null )
-        {
-            return;
-        }
-
-        for (IElementEventHandler handler : eventHandlers)
-        {
-            addElementEventHandler(handler);
-        }
-    }
-
-    @Override
-    public long getTimeFactorForMilliseconds()
-    {
-        return timeFactor;
-    }
-
-    @Override
-    public void setTimeFactorForMilliseconds(long factor)
-    {
-        this.timeFactor = factor;
-    }
-
-    /**
-     * Gets the elementEventHandlers. Returns null if none exist. Makes checking easy.
-     * <p>
-     * @return The elementEventHandlers List of IElementEventHandler objects
-     */
-    @Override
-    public ArrayList<IElementEventHandler> getElementEventHandlers()
-    {
-        return this.eventHandlers;
-    }
-
-    /**
-     * For logging and debugging the element IElementAttributes.
-     * <p>
-     * @return String info about the values.
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder dump = new StringBuilder();
-
-        dump.append( "[ IS_LATERAL = " ).append( IS_LATERAL );
-        dump.append( ", IS_SPOOL = " ).append( IS_SPOOL );
-        dump.append( ", IS_REMOTE = " ).append( IS_REMOTE );
-        dump.append( ", IS_ETERNAL = " ).append( IS_ETERNAL );
-        dump.append( ", MaxLifeSeconds = " ).append( this.getMaxLife() );
-        dump.append( ", IdleTime = " ).append( this.getIdleTime() );
-        dump.append( ", CreateTime = " ).append( this.getCreateTime() );
-        dump.append( ", LastAccessTime = " ).append( this.getLastAccessTime() );
-        dump.append( ", getTimeToLiveSeconds() = " ).append( String.valueOf( getTimeToLiveSeconds() ) );
-        dump.append( ", createTime = " ).append( String.valueOf( createTime ) ).append( " ]" );
-
-        return dump.toString();
-    }
-
-    /**
-     * @see java.lang.Object#clone()
-     */
-    @Override
-    public IElementAttributes clone()
-    {
-        try
-        {
-        	ElementAttributes c = (ElementAttributes) super.clone();
-        	c.setCreateTime();
-            return c;
-        }
-        catch (CloneNotSupportedException e)
-        {
-            throw new RuntimeException("Clone not supported. This should never happen.", e);
-        }
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/PooledCacheEventQueue.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/PooledCacheEventQueue.java
deleted file mode 100644
index cd94f7d..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/PooledCacheEventQueue.java
+++ /dev/null
@@ -1,191 +0,0 @@
-package org.apache.commons.jcs.engine;
-
-import java.util.ArrayList;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.ThreadPoolExecutor;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.behavior.ICacheListener;
-import org.apache.commons.jcs.engine.stats.StatElement;
-import org.apache.commons.jcs.engine.stats.Stats;
-import org.apache.commons.jcs.engine.stats.behavior.IStatElement;
-import org.apache.commons.jcs.engine.stats.behavior.IStats;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-import org.apache.commons.jcs.utils.threadpool.ThreadPoolManager;
-
-/**
- * An event queue is used to propagate ordered cache events to one and only one target listener.
- * <p>
- * This is a modified version of the experimental version. It uses a PooledExecutor and a
- * BoundedBuffer to queue up events and execute them as threads become available.
- * <p>
- * The PooledExecutor is static, because presumably these processes will be IO bound, so throwing
- * more than a few threads at them will serve no purpose other than to saturate the IO interface. In
- * light of this, having one thread per region seems unnecessary. This may prove to be false.
- */
-public class PooledCacheEventQueue<K, V>
-    extends AbstractCacheEventQueue<K, V>
-{
-    /** The logger. */
-    private static final Log log = LogManager.getLog( PooledCacheEventQueue.class );
-
-    /** The type of event queue */
-    private static final QueueType queueType = QueueType.POOLED;
-
-    /** The Thread Pool to execute events with. */
-    protected ExecutorService pool = null;
-
-    /** The Thread Pool queue */
-    protected BlockingQueue<Runnable> queue = null;
-
-    /**
-     * Constructor for the CacheEventQueue object
-     * <p>
-     * @param listener
-     * @param listenerId
-     * @param cacheName
-     * @param maxFailure
-     * @param waitBeforeRetry
-     * @param threadPoolName
-     */
-    public PooledCacheEventQueue( ICacheListener<K, V> listener, long listenerId, String cacheName, int maxFailure,
-                                  int waitBeforeRetry, String threadPoolName )
-    {
-        initialize( listener, listenerId, cacheName, maxFailure, waitBeforeRetry, threadPoolName );
-    }
-
-    /**
-     * Initializes the queue.
-     * <p>
-     * @param listener
-     * @param listenerId
-     * @param cacheName
-     * @param maxFailure
-     * @param waitBeforeRetry
-     * @param threadPoolName
-     */
-    protected void initialize( ICacheListener<K, V> listener, long listenerId, String cacheName, int maxFailure,
-                            int waitBeforeRetry, String threadPoolName )
-    {
-        super.initialize(listener, listenerId, cacheName, maxFailure, waitBeforeRetry);
-
-        // this will share the same pool with other event queues by default.
-        pool = ThreadPoolManager.getInstance().getExecutorService(
-                (threadPoolName == null) ? "cache_event_queue" : threadPoolName );
-
-        if (pool instanceof ThreadPoolExecutor)
-        {
-        	queue = ((ThreadPoolExecutor) pool).getQueue();
-        }
-    }
-
-    /**
-     * @return the queue type
-     */
-    @Override
-    public QueueType getQueueType()
-    {
-        return queueType;
-    }
-
-    /**
-     * Destroy the queue. Interrupt all threads.
-     */
-    @Override
-    public synchronized void destroy()
-    {
-        if ( isWorking() )
-        {
-            setWorking(false);
-            pool.shutdownNow();
-            log.info( "Cache event queue destroyed: {0}", this );
-        }
-    }
-
-    /**
-     * Adds an event to the queue.
-     * <p>
-     * @param event
-     */
-    @Override
-    protected void put( AbstractCacheEvent event )
-    {
-        pool.execute( event );
-    }
-
-    /**
-     * @return IStats
-     */
-    @Override
-    public IStats getStatistics()
-    {
-        IStats stats = new Stats();
-        stats.setTypeName( "Pooled Cache Event Queue" );
-
-        ArrayList<IStatElement<?>> elems = new ArrayList<>();
-
-        elems.add(new StatElement<>( "Working", Boolean.valueOf(isWorking()) ) );
-        elems.add(new StatElement<>( "Empty", Boolean.valueOf(this.isEmpty()) ) );
-
-        if ( queue != null )
-        {
-            elems.add(new StatElement<>( "Queue Size", Integer.valueOf(queue.size()) ) );
-            elems.add(new StatElement<>( "Queue Capacity", Integer.valueOf(queue.remainingCapacity()) ) );
-        }
-
-        stats.setStatElements( elems );
-
-        return stats;
-    }
-
-    /**
-     * If the Queue is using a bounded channel we can determine the size. If it is zero or we can't
-     * determine the size, we return true.
-     * <p>
-     * @return whether or not there are items in the queue
-     */
-    @Override
-    public boolean isEmpty()
-    {
-        return size() == 0;
-    }
-
-    /**
-     * Returns the number of elements in the queue. If the queue cannot determine the size
-     * accurately it will return 0.
-     * <p>
-     * @return number of items in the queue.
-     */
-    @Override
-    public int size()
-    {
-        if ( queue == null )
-        {
-            return 0;
-        }
-        else
-        {
-            return queue.size();
-        }
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/ZombieCacheService.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/ZombieCacheService.java
deleted file mode 100644
index 061da02..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/ZombieCacheService.java
+++ /dev/null
@@ -1,151 +0,0 @@
-package org.apache.commons.jcs.engine;
-
-import java.io.Serializable;
-import java.util.Collections;
-import java.util.Map;
-import java.util.Set;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheService;
-import org.apache.commons.jcs.engine.behavior.IZombie;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * Zombie adapter for any cache service. Balks at every call.
- */
-public class ZombieCacheService<K, V>
-    implements ICacheService<K, V>, IZombie
-{
-    /** The logger. */
-    private static final Log log = LogManager.getLog( ZombieCacheService.class );
-
-    /**
-     * @param item
-     */
-    public void put( ICacheElement<K, V> item )
-    {
-        log.debug( "Zombie put for item {0}", item );
-        // zombies have no inner life
-    }
-
-    /**
-     * Does nothing.
-     * <p>
-     * @param item
-     */
-    @Override
-    public void update( ICacheElement<K, V> item )
-    {
-        // zombies have no inner life
-    }
-
-    /**
-     * @param cacheName
-     * @param key
-     * @return null. zombies have no internal data
-     */
-    @Override
-    public ICacheElement<K, V> get( String cacheName, K key )
-    {
-        return null;
-    }
-
-    /**
-     * Returns an empty map. Zombies have no internal data.
-     * <p>
-     * @param cacheName
-     * @param keys
-     * @return Collections.EMPTY_MAP
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys )
-    {
-        return Collections.emptyMap();
-    }
-
-    /**
-     * Returns an empty map. Zombies have no internal data.
-     * <p>
-     * @param cacheName
-     * @param pattern
-     * @return Collections.EMPTY_MAP
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern )
-    {
-        return Collections.emptyMap();
-    }
-
-    /**
-     * Logs the get to debug, but always balks.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @param container
-     * @return null always
-     */
-    public Serializable get( String cacheName, K key, boolean container )
-    {
-        log.debug( "Zombie get for key [{0}] cacheName [{1}] container [{2}]",
-                key, cacheName, container);
-        // zombies have no inner life
-        return null;
-    }
-
-    /**
-     * @param cacheName
-     * @param key
-     */
-    @Override
-    public void remove( String cacheName, K key )
-    {
-        // zombies have no inner life
-    }
-
-    /**
-     * @param cacheName
-     */
-    @Override
-    public void removeAll( String cacheName )
-    {
-        // zombies have no inner life
-    }
-
-    /**
-     * @param cacheName
-     */
-    @Override
-    public void dispose( String cacheName )
-    {
-        // zombies have no inner life
-    }
-
-    /**
-     * Frees all caches.
-     */
-    @Override
-    public void release()
-    {
-        // zombies have no inner life
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/ZombieCacheServiceNonLocal.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/ZombieCacheServiceNonLocal.java
deleted file mode 100644
index 8aa5630..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/ZombieCacheServiceNonLocal.java
+++ /dev/null
@@ -1,316 +0,0 @@
-package org.apache.commons.jcs.engine;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentLinkedQueue;
-
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheServiceNonLocal;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-import org.apache.commons.jcs.utils.timing.ElapsedTimer;
-
-/**
- * Zombie adapter for the non local cache services. It just balks if there is no queue configured.
- * <p>
- * If a queue is configured, then events will be added to the queue. The idea is that when proper
- * operation is restored, the non local cache will walk the queue. The queue must be bounded so it
- * does not eat memory.
- * <p>
- * This originated in the remote cache.
- */
-public class ZombieCacheServiceNonLocal<K, V>
-    extends ZombieCacheService<K, V>
-    implements ICacheServiceNonLocal<K, V>
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog( ZombieCacheServiceNonLocal.class );
-
-    /** How big can the queue grow. */
-    private int maxQueueSize = 0;
-
-    /** The queue */
-    private final ConcurrentLinkedQueue<ZombieEvent> queue;
-
-    /**
-     * Default.
-     */
-    public ZombieCacheServiceNonLocal()
-    {
-        queue = new ConcurrentLinkedQueue<>();
-    }
-
-    /**
-     * Sets the maximum number of items that will be allowed on the queue.
-     * <p>
-     * @param maxQueueSize
-     */
-    public ZombieCacheServiceNonLocal( int maxQueueSize )
-    {
-        this.maxQueueSize = maxQueueSize;
-        queue = new ConcurrentLinkedQueue<>();
-    }
-
-    /**
-     * Gets the number of items on the queue.
-     * <p>
-     * @return size of the queue.
-     */
-    public int getQueueSize()
-    {
-        return queue.size();
-    }
-
-    private void addQueue(ZombieEvent event)
-    {
-        queue.add(event);
-        if (queue.size() > maxQueueSize)
-        {
-            queue.poll(); // drop oldest entry
-        }
-    }
-
-    /**
-     * Adds an update event to the queue if the maxSize is greater than 0;
-     * <p>
-     * @param item ICacheElement
-     * @param listenerId - identifies the caller.
-     */
-    @Override
-    public void update( ICacheElement<K, V> item, long listenerId )
-    {
-        if ( maxQueueSize > 0 )
-        {
-            PutEvent<K, V> event = new PutEvent<>( item, listenerId );
-            addQueue( event );
-        }
-        // Zombies have no inner life
-    }
-
-    /**
-     * Adds a removeAll event to the queue if the maxSize is greater than 0;
-     * <p>
-     * @param cacheName - region name
-     * @param key - item key
-     * @param listenerId - identifies the caller.
-     */
-    @Override
-    public void remove( String cacheName, K key, long listenerId )
-    {
-        if ( maxQueueSize > 0 )
-        {
-            RemoveEvent<K> event = new RemoveEvent<>( cacheName, key, listenerId );
-            addQueue( event );
-        }
-        // Zombies have no inner life
-    }
-
-    /**
-     * Adds a removeAll event to the queue if the maxSize is greater than 0;
-     * <p>
-     * @param cacheName - name of the region
-     * @param listenerId - identifies the caller.
-     */
-    @Override
-    public void removeAll( String cacheName, long listenerId )
-    {
-        if ( maxQueueSize > 0 )
-        {
-            RemoveAllEvent event = new RemoveAllEvent( cacheName, listenerId );
-            addQueue( event );
-        }
-        // Zombies have no inner life
-    }
-
-    /**
-     * Does nothing. Gets are synchronous and cannot be added to a queue.
-     * <p>
-     * @param cacheName - region name
-     * @param key - item key
-     * @param requesterId - identifies the caller.
-     * @return null
-     * @throws IOException
-     */
-    @Override
-    public ICacheElement<K, V> get( String cacheName, K key, long requesterId )
-        throws IOException
-    {
-        // Zombies have no inner life
-        return null;
-    }
-
-    /**
-     * Does nothing.
-     * <p>
-     * @param cacheName
-     * @param pattern
-     * @param requesterId
-     * @return empty map
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern, long requesterId )
-        throws IOException
-    {
-        return Collections.emptyMap();
-    }
-
-    /**
-     * @param cacheName - region name
-     * @param keys - item key
-     * @param requesterId - identity of the caller
-     * @return an empty map. zombies have no internal data
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys, long requesterId )
-    {
-        return new HashMap<>();
-    }
-
-    /**
-     * Does nothing.
-     * <p>
-     * @param cacheName - region name
-     * @return empty set
-     */
-    @Override
-    public Set<K> getKeySet( String cacheName )
-    {
-        return Collections.emptySet();
-    }
-
-    /**
-     * Walk the queue, calling the service for each queue operation.
-     * <p>
-     * @param service
-     * @throws Exception
-     */
-    public synchronized void propagateEvents( ICacheServiceNonLocal<K, V> service )
-        throws Exception
-    {
-        int cnt = 0;
-        log.info( "Propagating events to the new ICacheServiceNonLocal." );
-        ElapsedTimer timer = new ElapsedTimer();
-        while ( !queue.isEmpty() )
-        {
-            cnt++;
-
-            // for each item, call the appropriate service method
-            ZombieEvent event = queue.poll();
-
-            if ( event instanceof PutEvent )
-            {
-                @SuppressWarnings("unchecked") // Type checked by instanceof
-                PutEvent<K, V> putEvent = (PutEvent<K, V>) event;
-                service.update( putEvent.element, event.requesterId );
-            }
-            else if ( event instanceof RemoveEvent )
-            {
-                @SuppressWarnings("unchecked") // Type checked by instanceof
-                RemoveEvent<K> removeEvent = (RemoveEvent<K>) event;
-                service.remove( event.cacheName, removeEvent.key, event.requesterId );
-            }
-            else if ( event instanceof RemoveAllEvent )
-            {
-                service.removeAll( event.cacheName, event.requesterId );
-            }
-        }
-        log.info( "Propagated {0} events to the new ICacheServiceNonLocal in {1}",
-                cnt, timer.getElapsedTimeString() );
-    }
-
-    /**
-     * Base of the other events.
-     */
-    protected static abstract class ZombieEvent
-    {
-        /** The name of the region. */
-        String cacheName;
-
-        /** The id of the requester */
-        long requesterId;
-    }
-
-    /**
-     * A basic put event.
-     */
-    private static class PutEvent<K, V>
-        extends ZombieEvent
-    {
-        /** The element to put */
-        ICacheElement<K, V> element;
-
-        /**
-         * Set the element
-         * @param element
-         * @param requesterId
-         */
-        public PutEvent( ICacheElement<K, V> element, long requesterId )
-        {
-            this.requesterId = requesterId;
-            this.element = element;
-        }
-    }
-
-    /**
-     * A basic Remove event.
-     */
-    private static class RemoveEvent<K>
-        extends ZombieEvent
-    {
-        /** The key to remove */
-        K key;
-
-        /**
-         * Set the element
-         * @param cacheName
-         * @param key
-         * @param requesterId
-         */
-        public RemoveEvent( String cacheName, K key, long requesterId )
-        {
-            this.cacheName = cacheName;
-            this.requesterId = requesterId;
-            this.key = key;
-        }
-    }
-
-    /**
-     * A basic RemoveAll event.
-     */
-    private static class RemoveAllEvent
-        extends ZombieEvent
-    {
-        /**
-         * @param cacheName
-         * @param requesterId
-         */
-        public RemoveAllEvent( String cacheName, long requesterId )
-        {
-            this.cacheName = cacheName;
-            this.requesterId = requesterId;
-        }
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/ZombieCacheWatch.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/ZombieCacheWatch.java
deleted file mode 100644
index 8a17e57..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/ZombieCacheWatch.java
+++ /dev/null
@@ -1,73 +0,0 @@
-package org.apache.commons.jcs.engine;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.behavior.ICacheListener;
-import org.apache.commons.jcs.engine.behavior.ICacheObserver;
-import org.apache.commons.jcs.engine.behavior.IZombie;
-
-/**
- * Zombie Observer.
- */
-public class ZombieCacheWatch
-    implements ICacheObserver, IZombie
-{
-    /**
-     * Adds a feature to the CacheListener attribute of the ZombieCacheWatch object
-     * <p>
-     * @param cacheName The feature to be added to the CacheListener attribute
-     * @param obj The feature to be added to the CacheListener attribute
-     */
-    @Override
-    public <K, V> void addCacheListener( String cacheName, ICacheListener<K, V> obj )
-    {
-        // empty
-    }
-
-    /**
-     * Adds a feature to the CacheListener attribute of the ZombieCacheWatch object
-     * <p>
-     * @param obj The feature to be added to the CacheListener attribute
-     */
-    @Override
-    public <K, V> void addCacheListener( ICacheListener<K, V> obj )
-    {
-        // empty
-    }
-
-    /**
-     * @param cacheName
-     * @param obj
-     */
-    @Override
-    public <K, V> void removeCacheListener( String cacheName, ICacheListener<K, V> obj )
-    {
-        // empty
-    }
-
-    /**
-     * @param obj
-     */
-    @Override
-    public <K, V> void removeCacheListener( ICacheListener<K, V> obj )
-    {
-        // empty
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICache.java
deleted file mode 100644
index 1dd0b7f..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICache.java
+++ /dev/null
@@ -1,144 +0,0 @@
-package org.apache.commons.jcs.engine.behavior;
-
-import java.io.IOException;
-import java.util.Map;
-import java.util.Set;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.CacheStatus;
-import org.apache.commons.jcs.engine.match.behavior.IKeyMatcher;
-
-/**
- * This is the top level interface for all cache like structures. It defines the methods used
- * internally by JCS to access, modify, and instrument such structures.
- *
- * This allows for a suite of reusable components for accessing such structures, for example
- * asynchronous access via an event queue.
- */
-public interface ICache<K, V>
-    extends ICacheType
-{
-    /** Delimiter of a cache name component. This is used for hierarchical deletion */
-    String NAME_COMPONENT_DELIMITER = ":";
-
-    /**
-     * Puts an item to the cache.
-     *
-     * @param element
-     * @throws IOException
-     */
-    void update( ICacheElement<K, V> element )
-        throws IOException;
-
-    /**
-     * Gets an item from the cache.
-     *
-     * @param key
-     * @return a cache element, or null if there is no data in cache for this key
-     * @throws IOException
-     */
-    ICacheElement<K, V> get( K key )
-        throws IOException;
-
-    /**
-     * Gets multiple items from the cache based on the given set of keys.
-     *
-     * @param keys
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no data in cache for any of these keys
-     * @throws IOException
-     */
-    Map<K, ICacheElement<K, V>> getMultiple(Set<K> keys)
-        throws IOException;
-
-    /**
-     * Gets items from the cache matching the given pattern.  Items from memory will replace those from remote sources.
-     *
-     * This only works with string keys.  It's too expensive to do a toString on every key.
-     *
-     * Auxiliaries will do their best to handle simple expressions.  For instance, the JDBC disk cache will convert * to % and . to _
-     *
-     * @param pattern
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no data matching the pattern.
-     * @throws IOException
-     */
-    Map<K, ICacheElement<K, V>> getMatching(String pattern)
-        throws IOException;
-
-    /**
-     * Removes an item from the cache.
-     *
-     * @param key
-     * @return false if there was an error in removal
-     * @throws IOException
-     */
-    boolean remove( K key )
-        throws IOException;
-
-    /**
-     * Removes all cached items from the cache.
-     *
-     * @throws IOException
-     */
-    void removeAll()
-        throws IOException;
-
-    /**
-     * Prepares for shutdown.
-     * @throws IOException
-     */
-    void dispose()
-        throws IOException;
-
-    /**
-     * Returns the current cache size in number of elements.
-     *
-     * @return number of elements
-     */
-    int getSize();
-
-    /**
-     * Returns the cache status.
-     *
-     * @return Alive or Error
-     */
-    CacheStatus getStatus();
-
-    /**
-     * Returns the cache stats.
-     *
-     * @return String of important historical information.
-     */
-    String getStats();
-
-    /**
-     * Returns the cache name.
-     *
-     * @return usually the region name.
-     */
-    String getCacheName();
-
-    /**
-     * Sets the key matcher used by get matching.
-     *
-     * @param keyMatcher
-     */
-    void setKeyMatcher( IKeyMatcher<K> keyMatcher );
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICacheElement.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICacheElement.java
deleted file mode 100644
index acb82ea..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICacheElement.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package org.apache.commons.jcs.engine.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.Serializable;
-
-/**
- * Every item is the cache is wrapped in an ICacheElement. This contains
- * information about the element: the region name, the key, the value, and the
- * element attributes.
- * <p>
- * The element attributes have lots of useful information about each element,
- * such as when they were created, how long they have to live, and if they are
- * allowed to be spooled, etc.
- *
- */
-public interface ICacheElement<K, V>
-    extends Serializable
-{
-
-    /**
-     * Gets the cacheName attribute of the ICacheElement&lt;K, V&gt; object. The cacheName
-     * is also known as the region name.
-     *
-     * @return The cacheName value
-     */
-    String getCacheName();
-
-    /**
-     * Gets the key attribute of the ICacheElement&lt;K, V&gt; object
-     *
-     * @return The key value
-     */
-    K getKey();
-
-    /**
-     * Gets the val attribute of the ICacheElement&lt;K, V&gt; object
-     *
-     * @return The val value
-     */
-    V getVal();
-
-    /**
-     * Gets the attributes attribute of the ICacheElement&lt;K, V&gt; object
-     *
-     * @return The attributes value
-     */
-    IElementAttributes getElementAttributes();
-
-    /**
-     * Sets the attributes attribute of the ICacheElement&lt;K, V&gt; object
-     *
-     * @param attr
-     *            The new attributes value
-     */
-    void setElementAttributes( IElementAttributes attr );
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICacheElementSerialized.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICacheElementSerialized.java
deleted file mode 100644
index 1d80da6..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICacheElementSerialized.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package org.apache.commons.jcs.engine.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * This interface defines the behavior of the serialized element wrapper.
- * <p>
- * The value is stored as a byte array. This should allow for a variety of serialization mechanisms.
- * <p>
- * This currently extends ICacheElement&lt;K, V&gt; for backward compatibility.
- *<p>
- * @author Aaron Smuts
- */
-public interface ICacheElementSerialized<K, V>
-    extends ICacheElement<K, V>
-{
-    /**
-     * Gets the value attribute of the ICacheElementSerialized object. This is the value the client
-     * cached serialized by some mechanism.
-     *<p>
-     * @return The serialized value
-     */
-    byte[] getSerializedValue();
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICacheEventQueue.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICacheEventQueue.java
deleted file mode 100644
index 3676385..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICacheEventQueue.java
+++ /dev/null
@@ -1,125 +0,0 @@
-package org.apache.commons.jcs.engine.behavior;
-
-import java.io.IOException;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.stats.behavior.IStats;
-
-/**
- * Interface for a cache event queue. An event queue is used to propagate
- * ordered cache events to one and only one target listener.
- */
-public interface ICacheEventQueue<K, V>
-{
-    enum QueueType
-    {
-        /** Does not use a thread pool. */
-        SINGLE,
-
-        /** Uses a thread pool. */
-        POOLED
-    }
-
-    /**
-     * Return the type of event queue we are using, either single or pooled.
-     * <p>
-     * @return the queue type: single or pooled
-     */
-    QueueType getQueueType();
-
-    /**
-     * Adds a feature to the PutEvent attribute of the ICacheEventQueue object
-     * <p>
-     * @param ce
-     *            The feature to be added to the PutEvent attribute
-     * @throws IOException
-     */
-    void addPutEvent( ICacheElement<K, V> ce )
-        throws IOException;
-
-    /**
-     * Adds a feature to the RemoveEvent attribute of the ICacheEventQueue
-     * object
-     * <p>
-     * @param key
-     *            The feature to be added to the RemoveEvent attribute
-     * @throws IOException
-     */
-    void addRemoveEvent( K key )
-        throws IOException;
-
-    /**
-     * Adds a feature to the RemoveAllEvent attribute of the ICacheEventQueue
-     * object
-     * <p>
-     * @throws IOException
-     */
-    void addRemoveAllEvent()
-        throws IOException;
-
-    /**
-     * Adds a feature to the DisposeEvent attribute of the ICacheEventQueue
-     * object
-     * <p>
-     * @throws IOException
-     */
-    void addDisposeEvent()
-        throws IOException;
-
-    /**
-     * Gets the listenerId attribute of the ICacheEventQueue object
-     *
-     * @return The listenerId value
-     */
-    long getListenerId();
-
-    /** Description of the Method */
-    void destroy();
-
-    /**
-     * A Queue is working unless it has reached its max failure count.
-     * <p>
-     * @return boolean
-     */
-    boolean isWorking();
-
-    /**
-     * Returns the number of elements in the queue.  If the queue cannot
-     * determine the size accurately it will return 1.
-     * <p>
-     * @return number of items in the queue.
-     */
-    int size();
-
-    /**
-     * Are there elements in the queue.
-     * <p>
-     * @return true if there are stil elements.
-     */
-    boolean isEmpty();
-
-    /**
-     * Returns the historical and statistical data for an event queue cache.
-     * <p>
-     * @return IStats
-     */
-    IStats getStatistics();
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICacheListener.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICacheListener.java
deleted file mode 100644
index 5e15913..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICacheListener.java
+++ /dev/null
@@ -1,86 +0,0 @@
-package org.apache.commons.jcs.engine.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-
-/**
- * Used to receive a cache event notification.
- * <p>
- * Note: objects which implement this interface are local listeners to cache changes, whereas
- * objects which implement IRmiCacheListener are remote listeners to cache changes.
- */
-public interface ICacheListener<K, V>
-{
-    /**
-     * Notifies the subscribers for a cache entry update.
-     * <p>
-     * @param item
-     * @throws IOException
-     */
-    void handlePut( ICacheElement<K, V> item )
-        throws IOException;
-
-    /**
-     * Notifies the subscribers for a cache entry removal.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @throws IOException
-     */
-    void handleRemove( String cacheName, K key )
-        throws IOException;
-
-    /**
-     * Notifies the subscribers for a cache remove-all.
-     * <p>
-     * @param cacheName
-     * @throws IOException
-     */
-    void handleRemoveAll( String cacheName )
-        throws IOException;
-
-    /**
-     * Notifies the subscribers for freeing up the named cache.
-     * <p>
-     * @param cacheName
-     * @throws IOException
-     */
-    void handleDispose( String cacheName )
-        throws IOException;
-
-    /**
-     * sets unique identifier of listener home
-     * <p>
-     * @param id The new listenerId value
-     * @throws IOException
-     */
-    void setListenerId( long id )
-        throws IOException;
-
-    /**
-     * Gets the listenerId attribute of the ICacheListener object
-     * <p>
-     * @return The listenerId value
-     * @throws IOException
-     */
-    long getListenerId()
-        throws IOException;
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICacheObserver.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICacheObserver.java
deleted file mode 100644
index 11c9a05..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICacheObserver.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package org.apache.commons.jcs.engine.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-
-/**
- * Used to register interest in receiving cache changes. <br>
- * <br>
- * Note: server which implements this interface provides a local cache event
- * notification service, whereas server which implements IRmiCacheWatch provides
- * a remote cache event notification service.
- *
- */
-public interface ICacheObserver
-{
-    /**
-     * Subscribes to the specified cache.
-     *
-     * @param cacheName
-     *            the specified cache.
-     * @param obj
-     *            object to notify for cache changes.
-     * @throws IOException
-     */
-    <K, V> void addCacheListener( String cacheName, ICacheListener<K, V> obj )
-        throws IOException;
-
-    //, CacheNotFoundException;
-
-    /**
-     * Subscribes to all caches.
-     *
-     * @param obj
-     *            object to notify for all cache changes.
-     * @throws IOException
-     */
-    <K, V> void addCacheListener( ICacheListener<K, V> obj )
-        throws IOException;
-
-    /**
-     * Unsubscribes from the specified cache.
-     * @param cacheName
-     *
-     * @param obj
-     *            existing subscriber.
-     * @throws IOException
-     */
-    <K, V> void removeCacheListener( String cacheName, ICacheListener<K, V> obj )
-        throws IOException;
-
-    /**
-     * Unsubscribes from all caches.
-     *
-     * @param obj
-     *            existing subscriber.
-     * @throws IOException
-     */
-    <K, V> void removeCacheListener( ICacheListener<K, V> obj )
-        throws IOException;
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICacheService.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICacheService.java
deleted file mode 100644
index 9f4aca5..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICacheService.java
+++ /dev/null
@@ -1,117 +0,0 @@
-package org.apache.commons.jcs.engine.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.access.exception.ObjectExistsException;
-import org.apache.commons.jcs.access.exception.ObjectNotFoundException;
-
-import java.io.IOException;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Used to retrieve and update the cache.
- * <p>
- * Note: server which implements this interface provides a local cache service, whereas server which
- * implements IRmiCacheService provides a remote cache service.
- */
-public interface ICacheService<K, V>
-{
-    /**
-     * Puts a cache item to the cache.
-     * <p>
-     * @param item
-     * @throws ObjectExistsException
-     * @throws IOException
-     */
-    void update( ICacheElement<K, V> item )
-        throws ObjectExistsException, IOException;
-
-    /**
-     * Returns a cache bean from the specified cache; or null if the key does not exist.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @return the ICacheElement&lt;K, V&gt; or null if not found
-     * @throws ObjectNotFoundException
-     * @throws IOException
-     */
-    ICacheElement<K, V> get( String cacheName, K key )
-        throws ObjectNotFoundException, IOException;
-
-    /**
-     * Gets multiple items from the cache based on the given set of keys.
-     * <p>
-     * @param cacheName
-     * @param keys
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache for any of these keys
-     * @throws ObjectNotFoundException
-     * @throws IOException
-     */
-    Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys )
-        throws ObjectNotFoundException, IOException;
-
-    /**
-     * Gets multiple items from the cache matching the pattern.
-     * <p>
-     * @param cacheName
-     * @param pattern
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache matching the pattern.
-     * @throws IOException
-     */
-    Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern )
-        throws IOException;
-
-    /**
-     * Removes the given key from the specified cache.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @throws IOException
-     */
-    void remove( String cacheName, K key )
-        throws IOException;
-
-    /**
-     * Remove all keys from the specified cache.
-     * @param cacheName
-     * @throws IOException
-     */
-    void removeAll( String cacheName )
-        throws IOException;
-
-    /**
-     * Frees the specified cache.
-     * <p>
-     * @param cacheName
-     * @throws IOException
-     */
-    void dispose( String cacheName )
-        throws IOException;
-
-    /**
-     * Frees all caches.
-     * @throws IOException
-     */
-    void release()
-        throws IOException;
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICacheServiceAdmin.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICacheServiceAdmin.java
deleted file mode 100644
index 856ff51..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICacheServiceAdmin.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package org.apache.commons.jcs.engine.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-
-/**
- * Description of the Interface
- *
- */
-public interface ICacheServiceAdmin
-{
-
-    /**
-     * Gets the stats attribute of the ICacheServiceAdmin object
-     *
-     * @return The stats value
-     * @throws IOException
-     */
-    String getStats()
-        throws IOException;
-
-    /** Description of the Method
-     * @throws IOException*/
-    void shutdown()
-        throws IOException;
-
-    /** Description of the Method
-     * @param host
-     * @param port
-     * @throws IOException*/
-    void shutdown( String host, int port )
-        throws IOException;
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICacheServiceNonLocal.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICacheServiceNonLocal.java
deleted file mode 100644
index 4fbf29f..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICacheServiceNonLocal.java
+++ /dev/null
@@ -1,118 +0,0 @@
-package org.apache.commons.jcs.engine.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.rmi.Remote;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Used to retrieve and update non local caches, such as the remote and lateral caches. Unlike
- * ICacheService, the methods here have a requester id. This allows us to avoid propagating events
- * to ourself.
- * <p>
- * TODO consider not extending ICacheService
- */
-public interface ICacheServiceNonLocal<K, V>
-    extends Remote, ICacheService<K, V>
-{
-    /**
-     * Puts a cache item to the cache.
-     * <p>
-     * @param item
-     * @param requesterId
-     * @throws IOException
-     */
-    void update( ICacheElement<K, V> item, long requesterId )
-        throws IOException;
-
-    /**
-     * Removes the given key from the specified cache.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @param requesterId
-     * @throws IOException
-     */
-    void remove( String cacheName, K key, long requesterId )
-        throws IOException;
-
-    /**
-     * Remove all keys from the specified cache.
-     * <p>
-     * @param cacheName
-     * @param requesterId
-     * @throws IOException
-     */
-    void removeAll( String cacheName, long requesterId )
-        throws IOException;
-
-    /**
-     * Returns a cache bean from the specified cache; or null if the key does not exist.
-     * <p>
-     * Adding the requester id, allows the cache to determine the source of the get.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @param requesterId
-     * @return ICacheElement
-     * @throws IOException
-     */
-    ICacheElement<K, V> get( String cacheName, K key, long requesterId )
-        throws IOException;
-
-    /**
-     * Gets multiple items from the cache based on the given set of keys.
-     * <p>
-     * @param cacheName
-     * @param keys
-     * @param requesterId
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache for any of these keys
-     * @throws IOException
-     */
-    Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys, long requesterId )
-        throws IOException;
-
-    /**
-     * Gets multiple items from the cache matching the pattern.
-     * <p>
-     * @param cacheName
-     * @param pattern
-     * @param requesterId
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache matching the pattern.
-     * @throws IOException
-     */
-    Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern, long requesterId )
-        throws IOException;
-
-    /**
-     * Get a set of the keys for all elements in the cache.
-     * <p>
-     * @param cacheName the name of the cache
-     * @return a set of the key type
-     * TODO This should probably be done in chunks with a range passed in. This
-     *       will be a problem if someone puts a 1,000,000 or so items in a
-     *       region.
-     */
-    Set<K> getKeySet( String cacheName ) throws IOException;
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICacheType.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICacheType.java
deleted file mode 100644
index 3828f3a..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICacheType.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package org.apache.commons.jcs.engine.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * Interface implemented by a specific cache.
- *
- */
-public interface ICacheType
-{
-    enum CacheType {
-        /** Composite/ memory cache type, central hub. */
-        CACHE_HUB,
-
-        /** Disk cache type. */
-        DISK_CACHE,
-
-        /** Lateral cache type. */
-        LATERAL_CACHE,
-
-        /** Remote cache type. */
-        REMOTE_CACHE
-    }
-
-    /**
-     * Returns the cache type.
-     * <p>
-     * @return The cacheType value
-     */
-    CacheType getCacheType();
-
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICompositeCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICompositeCacheAttributes.java
deleted file mode 100644
index 7d22e4e..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICompositeCacheAttributes.java
+++ /dev/null
@@ -1,244 +0,0 @@
-package org.apache.commons.jcs.engine.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.Serializable;
-
-/**
- * This defines the minimal behavior for the Cache Configuration settings.
- */
-public interface ICompositeCacheAttributes
-    extends Serializable, Cloneable
-{
-    enum DiskUsagePattern
-    {
-        /** Items will only go to disk when the memory limit is reached. This is the default. */
-        SWAP,
-
-        /**
-         * Items will go to disk on a normal put. If The disk usage pattern is UPDATE, the swap will be
-         * disabled.
-         */
-        UPDATE
-    }
-
-    /**
-     * SetMaxObjects is used to set the attribute to determine the maximum
-     * number of objects allowed in the memory cache. If the max number of
-     * objects or the cache size is set, the default for the one not set is
-     * ignored. If both are set, both are used to determine the capacity of the
-     * cache, i.e., object will be removed from the cache if either limit is
-     * reached. TODO: move to MemoryCache config file.
-     * <p>
-     * @param size
-     *            The new maxObjects value
-     */
-    void setMaxObjects( int size );
-
-    /**
-     * Gets the maxObjects attribute of the ICompositeCacheAttributes object
-     * <p>
-     * @return The maxObjects value
-     */
-    int getMaxObjects();
-
-    /**
-     * Sets the useDisk attribute of the ICompositeCacheAttributes object
-     * <p>
-     * @param useDisk
-     *            The new useDisk value
-     */
-    void setUseDisk( boolean useDisk );
-
-    /**
-     * Gets the useDisk attribute of the ICompositeCacheAttributes object
-     * <p>
-     * @return The useDisk value
-     */
-    boolean isUseDisk();
-
-    /**
-     * set whether the cache should use a lateral cache
-     * <p>
-     * @param d
-     *            The new useLateral value
-     */
-    void setUseLateral( boolean d );
-
-    /**
-     * Gets the useLateral attribute of the ICompositeCacheAttributes object
-     * <p>
-     * @return The useLateral value
-     */
-    boolean isUseLateral();
-
-    /**
-     * Sets whether the cache is remote enabled
-     * <p>
-     * @param isRemote
-     *            The new useRemote value
-     */
-    void setUseRemote( boolean isRemote );
-
-    /**
-     * returns whether the cache is remote enabled
-     * <p>
-     * @return The useRemote value
-     */
-    boolean isUseRemote();
-
-    /**
-     * Sets the name of the cache, referenced by the appropriate manager.
-     * <p>
-     * @param s
-     *            The new cacheName value
-     */
-    void setCacheName( String s );
-
-    /**
-     * Gets the cacheName attribute of the ICompositeCacheAttributes object
-     * <p>
-     * @return The cacheName value
-     */
-    String getCacheName();
-
-    /**
-     * Sets the name of the MemoryCache, referenced by the appropriate manager.
-     * TODO: create a separate memory cache attribute class.
-     * <p>
-     * @param s
-     *            The new memoryCacheName value
-     */
-    void setMemoryCacheName( String s );
-
-    /**
-     * Gets the memoryCacheName attribute of the ICompositeCacheAttributes
-     * object
-     * <p>
-     * @return The memoryCacheName value
-     */
-    String getMemoryCacheName();
-
-    /**
-     * Whether the memory cache should perform background memory shrinkage.
-     * <p>
-     * @param useShrinker
-     *            The new UseMemoryShrinker value
-     */
-    void setUseMemoryShrinker( boolean useShrinker );
-
-    /**
-     * Whether the memory cache should perform background memory shrinkage.
-     * <p>
-     * @return The UseMemoryShrinker value
-     */
-    boolean isUseMemoryShrinker();
-
-    /**
-     * If UseMemoryShrinker is true the memory cache should auto-expire elements
-     * to reclaim space.
-     * <p>
-     * @param seconds
-     *            The new MaxMemoryIdleTimeSeconds value
-     */
-    void setMaxMemoryIdleTimeSeconds( long seconds );
-
-    /**
-     * If UseMemoryShrinker is true the memory cache should auto-expire elements
-     * to reclaim space.
-     * <p>
-     * @return The MaxMemoryIdleTimeSeconds value
-     */
-    long getMaxMemoryIdleTimeSeconds();
-
-    /**
-     * If UseMemoryShrinker is true the memory cache should auto-expire elements
-     * to reclaim space. This sets the shrinker interval.
-     * <p>
-     * @param seconds
-     *            The new ShrinkerIntervalSeconds value
-     */
-    void setShrinkerIntervalSeconds( long seconds );
-
-    /**
-     * If UseMemoryShrinker is true the memory cache should auto-expire elements
-     * to reclaim space. This gets the shrinker interval.
-     * <p>
-     * @return The ShrinkerIntervalSeconds value
-     */
-    long getShrinkerIntervalSeconds();
-
-    /**
-     * If UseMemoryShrinker is true the memory cache should auto-expire elements
-     * to reclaim space. This sets the maximum number of items to spool per run.
-     * <p>
-     * @param maxSpoolPerRun
-     *            The new maxSpoolPerRun value
-     */
-    void setMaxSpoolPerRun( int maxSpoolPerRun );
-
-    /**
-     * If UseMemoryShrinker is true the memory cache should auto-expire elements
-     * to reclaim space. This gets the maximum number of items to spool per run.
-     * <p>
-     * @return The maxSpoolPerRun value
-     */
-    int getMaxSpoolPerRun();
-
-    /**
-     * By default this is SWAP_ONLY.
-     * <p>
-     * @param diskUsagePattern The diskUsagePattern to set.
-     */
-    void setDiskUsagePattern( DiskUsagePattern diskUsagePattern );
-
-    /**
-     * Translates the name to the disk usage pattern short value.
-     * <p>
-     * The allowed values are SWAP and UPDATE.
-     * <p>
-     * @param diskUsagePatternName The diskUsagePattern to set.
-     */
-    void setDiskUsagePatternName( String diskUsagePatternName );
-
-    /**
-     * @return Returns the diskUsagePattern.
-     */
-    DiskUsagePattern getDiskUsagePattern();
-
-    /**
-     * Number to send to disk at at time when memory is full.
-     * <p>
-     * @return int
-     */
-    int getSpoolChunkSize();
-
-    /**
-     * Number to send to disk at a time.
-     * <p>
-     * @param spoolChunkSize
-     */
-    void setSpoolChunkSize( int spoolChunkSize );
-
-    /**
-     * Clone object
-     */
-    ICompositeCacheAttributes clone();
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICompositeCacheManager.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICompositeCacheManager.java
deleted file mode 100644
index caf499c..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/ICompositeCacheManager.java
+++ /dev/null
@@ -1,64 +0,0 @@
-package org.apache.commons.jcs.engine.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.AuxiliaryCache;
-import org.apache.commons.jcs.engine.control.CompositeCache;
-
-import java.util.Properties;
-
-/**
- * I need the interface so I can plug in mock managers for testing.
- *
- * @author Aaron Smuts
- */
-public interface ICompositeCacheManager extends IShutdownObservable
-{
-    /**
-     * Gets the cache attribute of the CacheHub object
-     *
-     * @param cacheName
-     * @return CompositeCache
-     */
-    <K, V> CompositeCache<K, V>  getCache( String cacheName );
-
-    /**
-     * Gets the auxiliary cache attribute of the CacheHub object
-     *
-     * @param auxName
-     * @param cacheName
-     * @return AuxiliaryCache
-     */
-    <K, V> AuxiliaryCache<K, V>  getAuxiliaryCache( String auxName, String cacheName );
-
-    /**
-     * This is exposed so other manager can get access to the props.
-     * <p>
-     * @return the configurationProperties
-     */
-    Properties getConfigurationProperties();
-
-    /**
-     * Gets stats for debugging.
-     * <p>
-     * @return String
-     */
-    String getStats();
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/IElementAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/IElementAttributes.java
deleted file mode 100644
index 116d187..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/IElementAttributes.java
+++ /dev/null
@@ -1,204 +0,0 @@
-package org.apache.commons.jcs.engine.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.commons.jcs.engine.control.event.behavior.IElementEventHandler;
-
-/**
- * Interface for cache element attributes classes. Every item is the cache is associated with an
- * element attributes object. It is used to track the life of the object as well as to restrict its
- * behavior. By default, elements get a clone of the region's attributes.
- */
-public interface IElementAttributes extends Serializable, Cloneable
-{
-    /**
-     * Sets the maxLife attribute of the IAttributes object.
-     * <p>
-     * @param mls The new MaxLifeSeconds value
-     */
-    void setMaxLife(long mls);
-
-    /**
-     * Sets the maxLife attribute of the IAttributes object. How many seconds it can live after
-     * creation.
-     * <p>
-     * If this is exceeded the element will not be returned, instead it will be removed. It will be
-     * removed on retrieval, or removed actively if the memory shrinker is turned on.
-     * @return The MaxLifeSeconds value
-     */
-    long getMaxLife();
-
-    /**
-     * Sets the idleTime attribute of the IAttributes object. This is the maximum time the item can
-     * be idle in the cache, that is not accessed.
-     * <p>
-     * If this is exceeded the element will not be returned, instead it will be removed. It will be
-     * removed on retrieval, or removed actively if the memory shrinker is turned on.
-     * @param idle The new idleTime value
-     */
-    void setIdleTime( long idle );
-
-    /**
-     * Size in bytes. This is not used except in the admin pages. It will be 0 by default
-     * and is only updated when the element is serialized.
-     * <p>
-     * @param size The new size value
-     */
-    void setSize( int size );
-
-    /**
-     * Gets the size attribute of the IAttributes object
-     * <p>
-     * @return The size value
-     */
-    int getSize();
-
-    /**
-     * Gets the createTime attribute of the IAttributes object.
-     * <p>
-     * This should be the current time in milliseconds returned by the sysutem call when the element
-     * is put in the cache.
-     * <p>
-     * Putting an item in the cache overrides any existing items.
-     * @return The createTime value
-     */
-    long getCreateTime();
-
-    /**
-     * Gets the LastAccess attribute of the IAttributes object.
-     * <p>
-     * @return The LastAccess value.
-     */
-    long getLastAccessTime();
-
-    /**
-     * Sets the LastAccessTime as now of the IElementAttributes object
-     */
-    void setLastAccessTimeNow();
-
-    /**
-     * Gets the idleTime attribute of the IAttributes object
-     * @return The idleTime value
-     */
-    long getIdleTime();
-
-    /**
-     * Gets the time left to live of the IAttributes object.
-     * <p>
-     * This is the (max life + create time) - current time.
-     * @return The TimeToLiveSeconds value
-     */
-    long getTimeToLiveSeconds();
-
-    /**
-     * Can this item be spooled to disk
-     * <p>
-     * By default this is true.
-     * @return The spoolable value
-     */
-    boolean getIsSpool();
-
-    /**
-     * Sets the isSpool attribute of the IElementAttributes object
-     * <p>
-     * By default this is true.
-     * @param val The new isSpool value
-     */
-    void setIsSpool( boolean val );
-
-    /**
-     * Is this item laterally distributable. Can it be sent to auxiliaries of type lateral.
-     * <p>
-     * By default this is true.
-     * @return The isLateral value
-     */
-    boolean getIsLateral();
-
-    /**
-     * Sets the isLateral attribute of the IElementAttributes object
-     * <p>
-     * By default this is true.
-     * @param val The new isLateral value
-     */
-    void setIsLateral( boolean val );
-
-    /**
-     * Can this item be sent to the remote cache.
-     * <p>
-     * By default this is true.
-     * @return The isRemote value
-     */
-    boolean getIsRemote();
-
-    /**
-     * Sets the isRemote attribute of the IElementAttributes object.
-     * <p>
-     * By default this is true.
-     * @param val The new isRemote value
-     */
-    void setIsRemote( boolean val );
-
-    /**
-     * This turns off expiration if it is true.
-     * @return The IsEternal value
-     */
-    boolean getIsEternal();
-
-    /**
-     * Sets the isEternal attribute of the IElementAttributes object
-     * @param val The new isEternal value
-     */
-    void setIsEternal( boolean val );
-
-    /**
-     * Adds a ElementEventHandler. Handler's can be registered for multiple events. A registered
-     * handler will be called at every recognized event.
-     * @param eventHandler The feature to be added to the ElementEventHandler
-     */
-    void addElementEventHandler( IElementEventHandler eventHandler );
-
-    /**
-     * Gets the elementEventHandlers.
-     * <p>
-     * Event handlers are transient. The only events defined are in memory events. All handlers are
-     * lost if the item goes to disk.
-     * @return The elementEventHandlers value, null if there are none
-     */
-    ArrayList<IElementEventHandler> getElementEventHandlers();
-
-    /**
-     * Sets the eventHandlers of the IElementAttributes object
-     * @param eventHandlers value
-     */
-    void addElementEventHandlers( List<IElementEventHandler> eventHandlers );
-
-    long getTimeFactorForMilliseconds();
-
-    void setTimeFactorForMilliseconds(long factor);
-
-    /**
-     * Clone object
-     */
-    IElementAttributes clone();
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/IElementSerializer.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/IElementSerializer.java
deleted file mode 100644
index 2a74713..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/IElementSerializer.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package org.apache.commons.jcs.engine.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-
-/**
- * Defines the behavior for cache element serializers. This layer of abstraction allows us to plug
- * in different serialization mechanisms, such as a compressing standard serializer.
- * <p>
- * @author Aaron Smuts
- */
-public interface IElementSerializer
-{
-    /**
-     * Turns an object into a byte array.
-     * @param obj
-     * @return byte[]
-     * @throws IOException
-     */
-    <T> byte[] serialize( T obj )
-        throws IOException;
-
-    /**
-     * Turns a byte array into an object.
-     * @param bytes data bytes
-     * @param loader class loader to use
-     * @return Object
-     * @throws IOException
-     * @throws ClassNotFoundException thrown if we don't know the object.
-     */
-    <T> T deSerialize( byte[] bytes, ClassLoader loader )
-        throws IOException, ClassNotFoundException;
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/IProvideScheduler.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/IProvideScheduler.java
deleted file mode 100644
index 29ad9d2..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/IProvideScheduler.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package org.apache.commons.jcs.engine.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.concurrent.ScheduledExecutorService;
-
-
-/**
- * Marker interface for providers of the central ScheduledExecutorService
- * <p>
- * @author Thomas Vandahl
- *
- */
-public interface IProvideScheduler
-{
-    /**
-     * Get an instance of a central ScheduledExecutorService
-     * @return the central scheduler
-     */
-    ScheduledExecutorService getScheduledExecutorService();
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/IRequireScheduler.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/IRequireScheduler.java
deleted file mode 100644
index 09ea8a7..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/IRequireScheduler.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.apache.commons.jcs.engine.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.concurrent.ScheduledExecutorService;
-
-
-/**
- * Marker interface to allow the injection of a central ScheduledExecutorService
- * for all modules requiring scheduled background operations.
- * <p>
- * @author Thomas Vandahl
- *
- */
-public interface IRequireScheduler
-{
-    /**
-     * Inject an instance of a central ScheduledExecutorService
-     * @param scheduledExecutor
-     */
-    void setScheduledExecutorService( ScheduledExecutorService scheduledExecutor );
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/IShutdownObservable.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/IShutdownObservable.java
deleted file mode 100644
index c428037..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/IShutdownObservable.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package org.apache.commons.jcs.engine.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * ShutdownObservers can observe ShutdownObservable objects.
- * The CacheManager is the primary observable that this is intended for.
- * <p>
- * Most shutdown operations will occur outside this framework for now.  The initial
- * goal is to allow background threads that are not reachable through any reference
- * that the cache manager maintains to be killed on shutdown.
- * <p>
- * Perhaps the composite cache itself should be the observable object.
- * It doesn't make much of a difference.  There are some problems with
- * region by region shutdown.  Some auxiliaries are local.  They will
- * need to track when every region has shutdown before doing things like
- * closing the socket with a lateral.
- * <p>
- * @author Aaron Smuts
- *
- */
-public interface IShutdownObservable
-{
-
-    /**
-     * Registers an observer with the observable object.
-     * @param observer
-     */
-    void registerShutdownObserver( IShutdownObserver observer );
-
-    /**
-     * Deregisters the observer with the observable.
-     *
-     * @param observer
-     */
-    void deregisterShutdownObserver( IShutdownObserver observer );
-
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/IShutdownObserver.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/IShutdownObserver.java
deleted file mode 100644
index 22b4052..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/IShutdownObserver.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package org.apache.commons.jcs.engine.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * This interface is required of all shutdown observers.  These observers
- * can observer ShutdownObservable objects.  The CacheManager is the primary
- * observable that this is intended for.
- * <p>
- * Most shutdown operations will occur outside this framework for now.  The initial
- * goal is to allow background threads that are not reachable through any reference
- * that the cache manager maintains to be killed on shutdown.
- *
- * @author Aaron Smuts
- *
- */
-public interface IShutdownObserver
-{
-    /**
-     * Tells the observer that the observable has received a shutdown command.
-     *
-     */
-    void shutdown();
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/IZombie.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/IZombie.java
deleted file mode 100644
index 2c726c6..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/IZombie.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package org.apache.commons.jcs.engine.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * Interface to mark an object as zombie for error recovery purposes.
- *
- */
-public interface IZombie
-{
-    // Zombies have no inner life.
-    // No qaulia found.
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/CompositeCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/CompositeCache.java
deleted file mode 100644
index 7601996..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/CompositeCache.java
+++ /dev/null
@@ -1,1693 +0,0 @@
-package org.apache.commons.jcs.engine.control;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import org.apache.commons.jcs.access.exception.CacheException;
-import org.apache.commons.jcs.access.exception.ObjectNotFoundException;
-import org.apache.commons.jcs.auxiliary.AuxiliaryCache;
-import org.apache.commons.jcs.engine.CacheStatus;
-import org.apache.commons.jcs.engine.behavior.ICache;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheAttributes;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheAttributes.DiskUsagePattern;
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-import org.apache.commons.jcs.engine.behavior.IRequireScheduler;
-import org.apache.commons.jcs.engine.control.event.ElementEvent;
-import org.apache.commons.jcs.engine.control.event.behavior.ElementEventType;
-import org.apache.commons.jcs.engine.control.event.behavior.IElementEvent;
-import org.apache.commons.jcs.engine.control.event.behavior.IElementEventHandler;
-import org.apache.commons.jcs.engine.control.event.behavior.IElementEventQueue;
-import org.apache.commons.jcs.engine.control.group.GroupId;
-import org.apache.commons.jcs.engine.match.KeyMatcherPatternImpl;
-import org.apache.commons.jcs.engine.match.behavior.IKeyMatcher;
-import org.apache.commons.jcs.engine.memory.behavior.IMemoryCache;
-import org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache;
-import org.apache.commons.jcs.engine.memory.shrinking.ShrinkerThread;
-import org.apache.commons.jcs.engine.stats.CacheStats;
-import org.apache.commons.jcs.engine.stats.StatElement;
-import org.apache.commons.jcs.engine.stats.behavior.ICacheStats;
-import org.apache.commons.jcs.engine.stats.behavior.IStatElement;
-import org.apache.commons.jcs.engine.stats.behavior.IStats;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * This is the primary hub for a single cache/region. It controls the flow of items through the
- * cache. The auxiliary and memory caches are plugged in here.
- * <p>
- * This is the core of a JCS region. Hence, this simple class is the core of JCS.
- */
-public class CompositeCache<K, V>
-    implements ICache<K, V>, IRequireScheduler
-{
-    /** log instance */
-    private static final Log log = LogManager.getLog(CompositeCache.class);
-
-    /**
-     * EventQueue for handling element events. Lazy initialized. One for each region. To be more efficient, the manager
-     * should pass a shared queue in.
-     */
-    private IElementEventQueue elementEventQ;
-
-    /** Auxiliary caches. */
-    @SuppressWarnings("unchecked") // OK because this is an empty array
-    private AuxiliaryCache<K, V>[] auxCaches = new AuxiliaryCache[0];
-
-    /** is this alive? */
-    private AtomicBoolean alive;
-
-    /** Region Elemental Attributes, default. */
-    private IElementAttributes attr;
-
-    /** Cache Attributes, for hub and memory auxiliary. */
-    private ICompositeCacheAttributes cacheAttr;
-
-    /** How many times update was called. */
-    private AtomicLong updateCount;
-
-    /** How many times remove was called. */
-    private AtomicLong removeCount;
-
-    /** Memory cache hit count */
-    private AtomicLong hitCountRam;
-
-    /** Auxiliary cache hit count (number of times found in ANY auxiliary) */
-    private AtomicLong hitCountAux;
-
-    /** Count of misses where element was not found. */
-    private AtomicLong missCountNotFound;
-
-    /** Count of misses where element was expired. */
-    private AtomicLong missCountExpired;
-
-    /** Cache manager. */
-    private CompositeCacheManager cacheManager = null;
-
-    /**
-     * The cache hub can only have one memory cache. This could be made more flexible in the future,
-     * but they are tied closely together. More than one doesn't make much sense.
-     */
-    private IMemoryCache<K, V> memCache;
-
-    /** Key matcher used by the getMatching API */
-    private IKeyMatcher<K> keyMatcher = new KeyMatcherPatternImpl<>();
-
-    private ScheduledFuture<?> future;
-
-    /**
-     * Constructor for the Cache object
-     * <p>
-     * @param cattr The cache attribute
-     * @param attr The default element attributes
-     */
-    public CompositeCache(ICompositeCacheAttributes cattr, IElementAttributes attr)
-    {
-        this.attr = attr;
-        this.cacheAttr = cattr;
-        this.alive = new AtomicBoolean(true);
-        this.updateCount = new AtomicLong(0);
-        this.removeCount = new AtomicLong(0);
-        this.hitCountRam = new AtomicLong(0);
-        this.hitCountAux = new AtomicLong(0);
-        this.missCountNotFound = new AtomicLong(0);
-        this.missCountExpired = new AtomicLong(0);
-
-        createMemoryCache(cattr);
-
-        log.info("Constructed cache with name [{0}] and cache attributes {1}",
-                cacheAttr.getCacheName(), cattr);
-    }
-
-    /**
-     * Injector for Element event queue
-     *
-     * @param queue
-     */
-    public void setElementEventQueue(IElementEventQueue queue)
-    {
-        this.elementEventQ = queue;
-    }
-
-    /**
-     * Injector for cache manager
-     *
-     * @param manager
-     */
-    public void setCompositeCacheManager(CompositeCacheManager manager)
-    {
-        this.cacheManager = manager;
-    }
-
-    /**
-     * @see org.apache.commons.jcs.engine.behavior.IRequireScheduler#setScheduledExecutorService(java.util.concurrent.ScheduledExecutorService)
-     */
-    @Override
-    public void setScheduledExecutorService(ScheduledExecutorService scheduledExecutor)
-    {
-        if (cacheAttr.isUseMemoryShrinker())
-        {
-            future = scheduledExecutor.scheduleAtFixedRate(
-                    new ShrinkerThread<>(this), 0, cacheAttr.getShrinkerIntervalSeconds(),
-                    TimeUnit.SECONDS);
-        }
-    }
-
-    /**
-     * This sets the list of auxiliary caches for this region.
-     * <p>
-     * @param auxCaches
-     */
-    public void setAuxCaches(AuxiliaryCache<K, V>[] auxCaches)
-    {
-        this.auxCaches = auxCaches;
-    }
-
-    /**
-     * Get the list of auxiliary caches for this region.
-     * <p>
-     * @return an array of auxiliary caches, may be empty, never null
-     */
-    public AuxiliaryCache<K, V>[] getAuxCaches()
-    {
-        return this.auxCaches;
-    }
-
-    /**
-     * Standard update method.
-     * <p>
-     * @param ce
-     * @throws IOException
-     */
-    @Override
-    public void update(ICacheElement<K, V> ce)
-        throws IOException
-    {
-        update(ce, false);
-    }
-
-    /**
-     * Standard update method.
-     * <p>
-     * @param ce
-     * @throws IOException
-     */
-    public void localUpdate(ICacheElement<K, V> ce)
-        throws IOException
-    {
-        update(ce, true);
-    }
-
-    /**
-     * Put an item into the cache. If it is localOnly, then do no notify remote or lateral
-     * auxiliaries.
-     * <p>
-     * @param cacheElement the ICacheElement&lt;K, V&gt;
-     * @param localOnly Whether the operation should be restricted to local auxiliaries.
-     * @throws IOException
-     */
-    protected void update(ICacheElement<K, V> cacheElement, boolean localOnly)
-        throws IOException
-    {
-
-        if (cacheElement.getKey() instanceof String
-            && cacheElement.getKey().toString().endsWith(NAME_COMPONENT_DELIMITER))
-        {
-            throw new IllegalArgumentException("key must not end with " + NAME_COMPONENT_DELIMITER
-                + " for a put operation");
-        }
-        else if (cacheElement.getKey() instanceof GroupId)
-        {
-            throw new IllegalArgumentException("key cannot be a GroupId " + " for a put operation");
-        }
-
-        log.debug("Updating memory cache {0}", () -> cacheElement.getKey());
-
-        updateCount.incrementAndGet();
-        memCache.update(cacheElement);
-        updateAuxiliaries(cacheElement, localOnly);
-
-        cacheElement.getElementAttributes().setLastAccessTimeNow();
-    }
-
-    /**
-     * This method is responsible for updating the auxiliaries if they are present. If it is local
-     * only, any lateral and remote auxiliaries will not be updated.
-     * <p>
-     * Before updating an auxiliary it checks to see if the element attributes permit the operation.
-     * <p>
-     * Disk auxiliaries are only updated if the disk cache is not merely used as a swap. If the disk
-     * cache is merely a swap, then items will only go to disk when they overflow from memory.
-     * <p>
-     * This is called by update(cacheElement, localOnly) after it updates the memory cache.
-     * <p>
-     * This is protected to make it testable.
-     * <p>
-     * @param cacheElement
-     * @param localOnly
-     * @throws IOException
-     */
-    protected void updateAuxiliaries(ICacheElement<K, V> cacheElement, boolean localOnly)
-        throws IOException
-    {
-        // UPDATE AUXILLIARY CACHES
-        // There are 3 types of auxiliary caches: remote, lateral, and disk
-        // more can be added if future auxiliary caches don't fit the model
-        // You could run a database cache as either a remote or a local disk.
-        // The types would describe the purpose.
-        if (auxCaches.length > 0)
-        {
-            log.debug("Updating auxiliary caches");
-        }
-        else
-        {
-            log.debug("No auxiliary cache to update");
-        }
-
-        for (ICache<K, V> aux : auxCaches)
-        {
-            if (aux == null)
-            {
-                continue;
-            }
-
-            log.debug("Auxiliary cache type: {0}", aux.getCacheType());
-
-            switch (aux.getCacheType())
-            {
-                // SEND TO REMOTE STORE
-                case REMOTE_CACHE:
-                    log.debug("ce.getElementAttributes().getIsRemote() = {0}",
-                        () -> cacheElement.getElementAttributes().getIsRemote());
-
-                    if (cacheElement.getElementAttributes().getIsRemote() && !localOnly)
-                    {
-                        try
-                        {
-                            // need to make sure the group cache understands that
-                            // the key is a group attribute on update
-                            aux.update(cacheElement);
-                            log.debug("Updated remote store for {0} {1}",
-                                    cacheElement.getKey(), cacheElement);
-                        }
-                        catch (IOException ex)
-                        {
-                            log.error("Failure in updateExclude", ex);
-                        }
-                    }
-                    break;
-
-                // SEND LATERALLY
-                case LATERAL_CACHE:
-                    // lateral can't do the checking since it is dependent on the
-                    // cache region restrictions
-                    log.debug("lateralcache in aux list: cattr {0}", () -> cacheAttr.isUseLateral());
-                    if (cacheAttr.isUseLateral() && cacheElement.getElementAttributes().getIsLateral() && !localOnly)
-                    {
-                        // DISTRIBUTE LATERALLY
-                        // Currently always multicast even if the value is
-                        // unchanged, to cause the cache item to move to the front.
-                        aux.update(cacheElement);
-                        log.debug("updated lateral cache for {0}", () -> cacheElement.getKey());
-                    }
-                    break;
-
-                // update disk if the usage pattern permits
-                case DISK_CACHE:
-                    log.debug("diskcache in aux list: cattr {0}", () -> cacheAttr.isUseDisk());
-                    if (cacheAttr.isUseDisk()
-                        && cacheAttr.getDiskUsagePattern() == DiskUsagePattern.UPDATE
-                        && cacheElement.getElementAttributes().getIsSpool())
-                    {
-                        aux.update(cacheElement);
-                        log.debug("updated disk cache for {0}", () -> cacheElement.getKey());
-                    }
-                    break;
-
-                default: // CACHE_HUB
-                    break;
-            }
-        }
-    }
-
-    /**
-     * Writes the specified element to any disk auxiliaries. Might want to rename this "overflow" in
-     * case the hub wants to do something else.
-     * <p>
-     * If JCS is not configured to use the disk as a swap, that is if the the
-     * CompositeCacheAttribute diskUsagePattern is not SWAP_ONLY, then the item will not be spooled.
-     * <p>
-     * @param ce The CacheElement
-     */
-    public void spoolToDisk(ICacheElement<K, V> ce)
-    {
-        // if the item is not spoolable, return
-        if (!ce.getElementAttributes().getIsSpool())
-        {
-            // there is an event defined for this.
-            handleElementEvent(ce, ElementEventType.SPOOLED_NOT_ALLOWED);
-            return;
-        }
-
-        boolean diskAvailable = false;
-
-        // SPOOL TO DISK.
-        for (ICache<K, V> aux : auxCaches)
-        {
-            if (aux != null && aux.getCacheType() == CacheType.DISK_CACHE)
-            {
-                diskAvailable = true;
-
-                if (cacheAttr.getDiskUsagePattern() == DiskUsagePattern.SWAP)
-                {
-                    // write the last items to disk.2
-                    try
-                    {
-                        handleElementEvent(ce, ElementEventType.SPOOLED_DISK_AVAILABLE);
-                        aux.update(ce);
-                    }
-                    catch (IOException ex)
-                    {
-                        // impossible case.
-                        log.error("Problem spooling item to disk cache.", ex);
-                        throw new IllegalStateException(ex.getMessage());
-                    }
-
-                    log.debug("spoolToDisk done for: {0} on disk cache[{1}]",
-                            () -> ce.getKey(), () -> aux.getCacheName());
-                }
-                else
-                {
-                    log.debug("DiskCache available, but JCS is not configured "
-                            + "to use the DiskCache as a swap.");
-                }
-            }
-        }
-
-        if (!diskAvailable)
-        {
-            handleElementEvent(ce, ElementEventType.SPOOLED_DISK_NOT_AVAILABLE);
-        }
-    }
-
-    /**
-     * Gets an item from the cache.
-     * <p>
-     * @param key
-     * @return element from the cache, or null if not present
-     * @see org.apache.commons.jcs.engine.behavior.ICache#get(Object)
-     */
-    @Override
-    public ICacheElement<K, V> get(K key)
-    {
-        return get(key, false);
-    }
-
-    /**
-     * Do not try to go remote or laterally for this get.
-     * <p>
-     * @param key
-     * @return ICacheElement
-     */
-    public ICacheElement<K, V> localGet(K key)
-    {
-        return get(key, true);
-    }
-
-    /**
-     * Look in memory, then disk, remote, or laterally for this item. The order is dependent on the
-     * order in the cache.ccf file.
-     * <p>
-     * Do not try to go remote or laterally for this get if it is localOnly. Otherwise try to go
-     * remote or lateral if such an auxiliary is configured for this region.
-     * <p>
-     * @param key
-     * @param localOnly
-     * @return ICacheElement
-     */
-    protected ICacheElement<K, V> get(K key, boolean localOnly)
-    {
-        ICacheElement<K, V> element = null;
-
-        boolean found = false;
-
-        log.debug("get: key = {0}, localOnly = {1}", key, localOnly);
-
-        try
-        {
-            // First look in memory cache
-            element = memCache.get(key);
-
-            if (element != null)
-            {
-                // Found in memory cache
-                if (isExpired(element))
-                {
-                    log.debug("{0} - Memory cache hit, but element expired",
-                            () -> cacheAttr.getCacheName());
-
-                    doExpires(element);
-                    element = null;
-                }
-                else
-                {
-                    log.debug("{0} - Memory cache hit", () -> cacheAttr.getCacheName());
-
-                    // Update counters
-                    hitCountRam.incrementAndGet();
-                }
-
-                found = true;
-            }
-            else
-            {
-                // Item not found in memory. If local invocation look in aux
-                // caches, even if not local look in disk auxiliaries
-                for (AuxiliaryCache<K, V> aux : auxCaches)
-                {
-                    if (aux != null)
-                    {
-                        CacheType cacheType = aux.getCacheType();
-
-                        if (!localOnly || cacheType == CacheType.DISK_CACHE)
-                        {
-                            log.debug("Attempting to get from aux [{0}] which is of type: {1}",
-                                    () -> aux.getCacheName(), () -> cacheType);
-
-                            try
-                            {
-                                element = aux.get(key);
-                            }
-                            catch (IOException e)
-                            {
-                                log.error("Error getting from aux", e);
-                            }
-                        }
-
-                        log.debug("Got CacheElement: {0}", element);
-
-                        // Item found in one of the auxiliary caches.
-                        if (element != null)
-                        {
-                            if (isExpired(element))
-                            {
-                                log.debug("{0} - Aux cache[{1}] hit, but element expired.",
-                                        () -> cacheAttr.getCacheName(), () -> aux.getCacheName());
-
-                                // This will tell the remotes to remove the item
-                                // based on the element's expiration policy. The elements attributes
-                                // associated with the item when it created govern its behavior
-                                // everywhere.
-                                doExpires(element);
-                                element = null;
-                            }
-                            else
-                            {
-                                log.debug("{0} - Aux cache[{1}] hit.",
-                                        () -> cacheAttr.getCacheName(), () -> aux.getCacheName());
-
-                                // Update counters
-                                hitCountAux.incrementAndGet();
-                                copyAuxiliaryRetrievedItemToMemory(element);
-                            }
-
-                            found = true;
-
-                            break;
-                        }
-                    }
-                }
-            }
-        }
-        catch (IOException e)
-        {
-            log.error("Problem encountered getting element.", e);
-        }
-
-        if (!found)
-        {
-            missCountNotFound.incrementAndGet();
-
-            log.debug("{0} - Miss", () -> cacheAttr.getCacheName());
-        }
-
-        if (element != null)
-        {
-            element.getElementAttributes().setLastAccessTimeNow();
-        }
-
-        return element;
-    }
-
-    protected void doExpires(ICacheElement<K, V> element)
-    {
-        missCountExpired.incrementAndGet();
-        remove(element.getKey());
-    }
-
-    /**
-     * Gets multiple items from the cache based on the given set of keys.
-     * <p>
-     * @param keys
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache for any of these keys
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMultiple(Set<K> keys)
-    {
-        return getMultiple(keys, false);
-    }
-
-    /**
-     * Gets multiple items from the cache based on the given set of keys. Do not try to go remote or
-     * laterally for this data.
-     * <p>
-     * @param keys
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache for any of these keys
-     */
-    public Map<K, ICacheElement<K, V>> localGetMultiple(Set<K> keys)
-    {
-        return getMultiple(keys, true);
-    }
-
-    /**
-     * Look in memory, then disk, remote, or laterally for these items. The order is dependent on
-     * the order in the cache.ccf file. Keep looking in each cache location until either the element
-     * is found, or the method runs out of places to look.
-     * <p>
-     * Do not try to go remote or laterally for this get if it is localOnly. Otherwise try to go
-     * remote or lateral if such an auxiliary is configured for this region.
-     * <p>
-     * @param keys
-     * @param localOnly
-     * @return ICacheElement
-     */
-    protected Map<K, ICacheElement<K, V>> getMultiple(Set<K> keys, boolean localOnly)
-    {
-        Map<K, ICacheElement<K, V>> elements = new HashMap<>();
-
-        log.debug("get: key = {0}, localOnly = {1}", keys, localOnly);
-
-        try
-        {
-            // First look in memory cache
-            elements.putAll(getMultipleFromMemory(keys));
-
-            // If fewer than all items were found in memory, then keep looking.
-            if (elements.size() != keys.size())
-            {
-                Set<K> remainingKeys = pruneKeysFound(keys, elements);
-                elements.putAll(getMultipleFromAuxiliaryCaches(remainingKeys, localOnly));
-            }
-        }
-        catch (IOException e)
-        {
-            log.error("Problem encountered getting elements.", e);
-        }
-
-        // if we didn't find all the elements, increment the miss count by the number of elements not found
-        if (elements.size() != keys.size())
-        {
-            missCountNotFound.addAndGet(keys.size() - elements.size());
-
-            log.debug("{0} - {1} Misses", () -> cacheAttr.getCacheName(),
-                    () -> keys.size() - elements.size());
-        }
-
-        return elements;
-    }
-
-    /**
-     * Gets items for the keys in the set. Returns a map: key -> result.
-     * <p>
-     * @param keys
-     * @return the elements found in the memory cache
-     * @throws IOException
-     */
-    private Map<K, ICacheElement<K, V>> getMultipleFromMemory(Set<K> keys)
-        throws IOException
-    {
-        Map<K, ICacheElement<K, V>> elementsFromMemory = memCache.getMultiple(keys);
-        elementsFromMemory.entrySet().removeIf(entry -> {
-            ICacheElement<K, V> element = entry.getValue();
-            if (isExpired(element))
-            {
-                log.debug("{0} - Memory cache hit, but element expired",
-                        () -> cacheAttr.getCacheName());
-
-                doExpires(element);
-                return true;
-            }
-            else
-            {
-                log.debug("{0} - Memory cache hit", () -> cacheAttr.getCacheName());
-
-                // Update counters
-                hitCountRam.incrementAndGet();
-                return false;
-            }
-        });
-
-        return elementsFromMemory;
-    }
-
-    /**
-     * If local invocation look in aux caches, even if not local look in disk auxiliaries.
-     * <p>
-     * @param keys
-     * @param localOnly
-     * @return the elements found in the auxiliary caches
-     * @throws IOException
-     */
-    private Map<K, ICacheElement<K, V>> getMultipleFromAuxiliaryCaches(Set<K> keys, boolean localOnly)
-        throws IOException
-    {
-        Map<K, ICacheElement<K, V>> elements = new HashMap<>();
-        Set<K> remainingKeys = new HashSet<>(keys);
-
-        for (AuxiliaryCache<K, V> aux : auxCaches)
-        {
-            if (aux != null)
-            {
-                Map<K, ICacheElement<K, V>> elementsFromAuxiliary =
-                    new HashMap<>();
-
-                CacheType cacheType = aux.getCacheType();
-
-                if (!localOnly || cacheType == CacheType.DISK_CACHE)
-                {
-                    log.debug("Attempting to get from aux [{0}] which is of type: {1}",
-                            () -> aux.getCacheName(), () -> cacheType);
-
-                    try
-                    {
-                        elementsFromAuxiliary.putAll(aux.getMultiple(remainingKeys));
-                    }
-                    catch (IOException e)
-                    {
-                        log.error("Error getting from aux", e);
-                    }
-                }
-
-                log.debug("Got CacheElements: {0}", elementsFromAuxiliary);
-
-                processRetrievedElements(aux, elementsFromAuxiliary);
-                elements.putAll(elementsFromAuxiliary);
-
-                if (elements.size() == keys.size())
-                {
-                    break;
-                }
-                else
-                {
-                    remainingKeys = pruneKeysFound(keys, elements);
-                }
-            }
-        }
-
-        return elements;
-    }
-
-    /**
-     * Build a map of all the matching elements in all of the auxiliaries and memory.
-     * <p>
-     * @param pattern
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache for any matching keys
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMatching(String pattern)
-    {
-        return getMatching(pattern, false);
-    }
-
-    /**
-     * Build a map of all the matching elements in all of the auxiliaries and memory. Do not try to
-     * go remote or laterally for this data.
-     * <p>
-     * @param pattern
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache for any matching keys
-     */
-    public Map<K, ICacheElement<K, V>> localGetMatching(String pattern)
-    {
-        return getMatching(pattern, true);
-    }
-
-    /**
-     * Build a map of all the matching elements in all of the auxiliaries and memory. Items in
-     * memory will replace from the auxiliaries in the returned map. The auxiliaries are accessed in
-     * opposite order. It's assumed that those closer to home are better.
-     * <p>
-     * Do not try to go remote or laterally for this get if it is localOnly. Otherwise try to go
-     * remote or lateral if such an auxiliary is configured for this region.
-     * <p>
-     * @param pattern
-     * @param localOnly
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache for any matching keys
-     */
-    protected Map<K, ICacheElement<K, V>> getMatching(String pattern, boolean localOnly)
-    {
-        log.debug("get: pattern [{0}], localOnly = {1}", pattern, localOnly);
-
-        try
-        {
-            return Stream.concat(
-                    getMatchingFromMemory(pattern).entrySet().stream(),
-                    getMatchingFromAuxiliaryCaches(pattern, localOnly).entrySet().stream())
-                    .collect(Collectors.toMap(
-                            entry -> entry.getKey(),
-                            entry -> entry.getValue(),
-                            // Prefer memory entries
-                            (mem, aux) -> mem));
-        }
-        catch (IOException e)
-        {
-            log.error("Problem encountered getting elements.", e);
-        }
-
-        return new HashMap<>();
-    }
-
-    /**
-     * Gets the key array from the memcache. Builds a set of matches. Calls getMultiple with the
-     * set. Returns a map: key -&gt; result.
-     * <p>
-     * @param pattern
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache for any matching keys
-     * @throws IOException
-     */
-    protected Map<K, ICacheElement<K, V>> getMatchingFromMemory(String pattern)
-        throws IOException
-    {
-        // find matches in key array
-        // this avoids locking the memory cache, but it uses more memory
-        Set<K> keyArray = memCache.getKeySet();
-        Set<K> matchingKeys = getKeyMatcher().getMatchingKeysFromArray(pattern, keyArray);
-
-        // call get multiple
-        return getMultipleFromMemory(matchingKeys);
-    }
-
-    /**
-     * If local invocation look in aux caches, even if not local look in disk auxiliaries.
-     * <p>
-     * Moves in reverse order of definition. This will allow you to override those that are from the
-     * remote with those on disk.
-     * <p>
-     * @param pattern
-     * @param localOnly
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache for any matching keys
-     * @throws IOException
-     */
-    private Map<K, ICacheElement<K, V>> getMatchingFromAuxiliaryCaches(String pattern, boolean localOnly)
-        throws IOException
-    {
-        Map<K, ICacheElement<K, V>> elements = new HashMap<>();
-
-        for (int i = auxCaches.length - 1; i >= 0; i--)
-        {
-            AuxiliaryCache<K, V> aux = auxCaches[i];
-
-            if (aux != null)
-            {
-                Map<K, ICacheElement<K, V>> elementsFromAuxiliary =
-                    new HashMap<>();
-
-                CacheType cacheType = aux.getCacheType();
-
-                if (!localOnly || cacheType == CacheType.DISK_CACHE)
-                {
-                    log.debug("Attempting to get from aux [{0}] which is of type: {1}",
-                            () -> aux.getCacheName(), () -> cacheType);
-
-                    try
-                    {
-                        elementsFromAuxiliary.putAll(aux.getMatching(pattern));
-                    }
-                    catch (IOException e)
-                    {
-                        log.error("Error getting from aux", e);
-                    }
-
-                    log.debug("Got CacheElements: {0}", elementsFromAuxiliary);
-
-                    processRetrievedElements(aux, elementsFromAuxiliary);
-                    elements.putAll(elementsFromAuxiliary);
-                }
-            }
-        }
-
-        return elements;
-    }
-
-    /**
-     * Remove expired elements retrieved from an auxiliary. Update memory with good items.
-     * <p>
-     * @param aux the auxiliary cache instance
-     * @param elementsFromAuxiliary
-     * @throws IOException
-     */
-    private void processRetrievedElements(AuxiliaryCache<K, V> aux, Map<K, ICacheElement<K, V>> elementsFromAuxiliary)
-        throws IOException
-    {
-        elementsFromAuxiliary.entrySet().removeIf(entry -> {
-            ICacheElement<K, V> element = entry.getValue();
-
-            // Item found in one of the auxiliary caches.
-            if (element != null)
-            {
-                if (isExpired(element))
-                {
-                    log.debug("{0} - Aux cache[{1}] hit, but element expired.",
-                            () -> cacheAttr.getCacheName(), () -> aux.getCacheName());
-
-                    // This will tell the remote caches to remove the item
-                    // based on the element's expiration policy. The elements attributes
-                    // associated with the item when it created govern its behavior
-                    // everywhere.
-                    doExpires(element);
-                    return true;
-                }
-                else
-                {
-                    log.debug("{0} - Aux cache[{1}] hit.",
-                            () -> cacheAttr.getCacheName(), () -> aux.getCacheName());
-
-                    // Update counters
-                    hitCountAux.incrementAndGet();
-                    try
-                    {
-                        copyAuxiliaryRetrievedItemToMemory(element);
-                    }
-                    catch (IOException e)
-                    {
-                        log.error("{0} failed to copy element to memory {1}",
-                                cacheAttr.getCacheName(), element, e);
-                    }
-                }
-            }
-
-            return false;
-        });
-    }
-
-    /**
-     * Copies the item to memory if the memory size is greater than 0. Only spool if the memory
-     * cache size is greater than 0, else the item will immediately get put into purgatory.
-     * <p>
-     * @param element
-     * @throws IOException
-     */
-    private void copyAuxiliaryRetrievedItemToMemory(ICacheElement<K, V> element)
-        throws IOException
-    {
-        if (memCache.getCacheAttributes().getMaxObjects() > 0)
-        {
-            memCache.update(element);
-        }
-        else
-        {
-            log.debug("Skipping memory update since no items are allowed in memory");
-        }
-    }
-
-    /**
-     * Returns a set of keys that were not found.
-     * <p>
-     * @param keys
-     * @param foundElements
-     * @return the original set of cache keys, minus any cache keys present in the map keys of the
-     *         foundElements map
-     */
-    private Set<K> pruneKeysFound(Set<K> keys, Map<K, ICacheElement<K, V>> foundElements)
-    {
-        Set<K> remainingKeys = new HashSet<>(keys);
-        remainingKeys.removeAll(foundElements.keySet());
-
-        return remainingKeys;
-    }
-
-    /**
-     * Get a set of the keys for all elements in the cache
-     * <p>
-     * @return A set of the key type
-     */
-    public Set<K> getKeySet()
-    {
-        return getKeySet(false);
-    }
-
-    /**
-     * Get a set of the keys for all elements in the cache
-     * <p>
-     * @param localOnly true if only memory keys are requested
-     *
-     * @return A set of the key type
-     */
-    public Set<K> getKeySet(boolean localOnly)
-    {
-        HashSet<K> allKeys = new HashSet<>();
-
-        allKeys.addAll(memCache.getKeySet());
-        for (AuxiliaryCache<K, V> aux : auxCaches)
-        {
-            if (aux != null)
-            {
-                if(!localOnly || aux.getCacheType() == CacheType.DISK_CACHE)
-                {
-                    try
-                    {
-                        allKeys.addAll(aux.getKeySet());
-                    }
-                    catch (IOException e)
-                    {
-                        // ignore
-                    }
-                }
-            }
-        }
-        return allKeys;
-    }
-
-    /**
-     * Removes an item from the cache.
-     * <p>
-     * @param key
-     * @return true is it was removed
-     * @see org.apache.commons.jcs.engine.behavior.ICache#remove(Object)
-     */
-    @Override
-    public boolean remove(K key)
-    {
-        return remove(key, false);
-    }
-
-    /**
-     * Do not propagate removeall laterally or remotely.
-     * <p>
-     * @param key
-     * @return true if the item was already in the cache.
-     */
-    public boolean localRemove(K key)
-    {
-        return remove(key, true);
-    }
-
-    /**
-     * fromRemote: If a remove call was made on a cache with both, then the remote should have been
-     * called. If it wasn't then the remote is down. we'll assume it is down for all. If it did come
-     * from the remote then the cache is remotely configured and lateral removal is unnecessary. If
-     * it came laterally then lateral removal is unnecessary. Does this assume that there is only
-     * one lateral and remote for the cache? Not really, the initial removal should take care of the
-     * problem if the source cache was similarly configured. Otherwise the remote cache, if it had
-     * no laterals, would remove all the elements from remotely configured caches, but if those
-     * caches had some other weird laterals that were not remotely configured, only laterally
-     * propagated then they would go out of synch. The same could happen for multiple remotes. If
-     * this looks necessary we will need to build in an identifier to specify the source of a
-     * removal.
-     * <p>
-     * @param key
-     * @param localOnly
-     * @return true if the item was in the cache, else false
-     */
-    protected boolean remove(K key, boolean localOnly)
-    {
-        removeCount.incrementAndGet();
-
-        boolean removed = false;
-
-        try
-        {
-            removed = memCache.remove(key);
-        }
-        catch (IOException e)
-        {
-            log.error(e);
-        }
-
-        // Removes from all auxiliary caches.
-        for (ICache<K, V> aux : auxCaches)
-        {
-            if (aux == null)
-            {
-                continue;
-            }
-
-            CacheType cacheType = aux.getCacheType();
-
-            // for now let laterals call remote remove but not vice versa
-            if (localOnly && (cacheType == CacheType.REMOTE_CACHE || cacheType == CacheType.LATERAL_CACHE))
-            {
-                continue;
-            }
-            try
-            {
-                log.debug("Removing {0} from cacheType {1}", key, cacheType);
-
-                boolean b = aux.remove(key);
-
-                // Don't take the remote removal into account.
-                if (!removed && cacheType != CacheType.REMOTE_CACHE)
-                {
-                    removed = b;
-                }
-            }
-            catch (IOException ex)
-            {
-                log.error("Failure removing from aux", ex);
-            }
-        }
-
-        return removed;
-    }
-
-    /**
-     * Clears the region. This command will be sent to all auxiliaries. Some auxiliaries, such as
-     * the JDBC disk cache, can be configured to not honor removeAll requests.
-     * <p>
-     * @see org.apache.commons.jcs.engine.behavior.ICache#removeAll()
-     */
-    @Override
-    public void removeAll()
-        throws IOException
-    {
-        removeAll(false);
-    }
-
-    /**
-     * Will not pass the remove message remotely.
-     * <p>
-     * @throws IOException
-     */
-    public void localRemoveAll()
-        throws IOException
-    {
-        removeAll(true);
-    }
-
-    /**
-     * Removes all cached items.
-     * <p>
-     * @param localOnly must pass in false to get remote and lateral aux's updated. This prevents
-     *            looping.
-     * @throws IOException
-     */
-    protected void removeAll(boolean localOnly)
-        throws IOException
-    {
-        try
-        {
-            memCache.removeAll();
-
-            log.debug("Removed All keys from the memory cache.");
-        }
-        catch (IOException ex)
-        {
-            log.error("Trouble updating memory cache.", ex);
-        }
-
-        // Removes from all auxiliary disk caches.
-        for (ICache<K, V> aux : auxCaches)
-        {
-            if (aux != null && (aux.getCacheType() == CacheType.DISK_CACHE || !localOnly))
-            {
-                try
-                {
-                    log.debug("Removing All keys from cacheType {0}",
-                            () -> aux.getCacheType());
-
-                    aux.removeAll();
-                }
-                catch (IOException ex)
-                {
-                    log.error("Failure removing all from aux", ex);
-                }
-            }
-        }
-    }
-
-    /**
-     * Flushes all cache items from memory to auxiliary caches and close the auxiliary caches.
-     */
-    @Override
-    public void dispose()
-    {
-        dispose(false);
-    }
-
-    /**
-     * Invoked only by CacheManager. This method disposes of the auxiliaries one by one. For the
-     * disk cache, the items in memory are freed, meaning that they will be sent through the
-     * overflow channel to disk. After the auxiliaries are disposed, the memory cache is disposed.
-     * <p>
-     * @param fromRemote
-     */
-    public void dispose(boolean fromRemote)
-    {
-         // If already disposed, return immediately
-        if (alive.compareAndSet(true, false) == false)
-        {
-            return;
-        }
-
-        log.info("In DISPOSE, [{0}] fromRemote [{1}]",
-                () -> this.cacheAttr.getCacheName(), () -> fromRemote);
-
-        // Remove us from the cache managers list
-        // This will call us back but exit immediately
-        if (cacheManager != null)
-        {
-            cacheManager.freeCache(getCacheName(), fromRemote);
-        }
-
-        // Try to stop shrinker thread
-        if (future != null)
-        {
-            future.cancel(true);
-        }
-
-        // Now, shut down the event queue
-        if (elementEventQ != null)
-        {
-            elementEventQ.dispose();
-            elementEventQ = null;
-        }
-
-        // Dispose of each auxiliary cache, Remote auxiliaries will be
-        // skipped if 'fromRemote' is true.
-        for (ICache<K, V> aux : auxCaches)
-        {
-            try
-            {
-                // Skip this auxiliary if:
-                // - The auxiliary is null
-                // - The auxiliary is not alive
-                // - The auxiliary is remote and the invocation was remote
-                if (aux == null || aux.getStatus() != CacheStatus.ALIVE
-                    || (fromRemote && aux.getCacheType() == CacheType.REMOTE_CACHE))
-                {
-                    log.info("In DISPOSE, [{0}] SKIPPING auxiliary [{1}] fromRemote [{2}]",
-                            () -> this.cacheAttr.getCacheName(), () -> aux.getCacheName(),
-                            () -> fromRemote);
-                    continue;
-                }
-
-                log.info("In DISPOSE, [{0}] auxiliary [{1}]",
-                        () -> this.cacheAttr.getCacheName(), () -> aux.getCacheName());
-
-                // IT USED TO BE THE CASE THAT (If the auxiliary is not a lateral, or the cache
-                // attributes
-                // have 'getUseLateral' set, all the elements currently in
-                // memory are written to the lateral before disposing)
-                // I changed this. It was excessive. Only the disk cache needs the items, since only
-                // the disk cache is in a situation to not get items on a put.
-                if (aux.getCacheType() == CacheType.DISK_CACHE)
-                {
-                    int numToFree = memCache.getSize();
-                    memCache.freeElements(numToFree);
-
-                    log.info("In DISPOSE, [{0}] put {1} into auxiliary [{2}]",
-                            () -> this.cacheAttr.getCacheName(), () -> numToFree,
-                            () -> aux.getCacheName());
-                }
-
-                // Dispose of the auxiliary
-                aux.dispose();
-            }
-            catch (IOException ex)
-            {
-                log.error("Failure disposing of aux.", ex);
-            }
-        }
-
-        log.info("In DISPOSE, [{0}] disposing of memory cache.",
-                () -> this.cacheAttr.getCacheName());
-        try
-        {
-            memCache.dispose();
-        }
-        catch (IOException ex)
-        {
-            log.error("Failure disposing of memCache", ex);
-        }
-    }
-
-    /**
-     * Calling save cause the entire contents of the memory cache to be flushed to all auxiliaries.
-     * Though this put is extremely fast, this could bog the cache and should be avoided. The
-     * dispose method should call a version of this. Good for testing.
-     */
-    public void save()
-    {
-        if (alive.compareAndSet(true, false) == false)
-        {
-            return;
-        }
-
-        for (ICache<K, V> aux : auxCaches)
-        {
-            try
-            {
-                if (aux.getStatus() == CacheStatus.ALIVE)
-                {
-                    for (K key : memCache.getKeySet())
-                    {
-                        ICacheElement<K, V> ce = memCache.get(key);
-
-                        if (ce != null)
-                        {
-                            aux.update(ce);
-                        }
-                    }
-                }
-            }
-            catch (IOException ex)
-            {
-                log.error("Failure saving aux caches.", ex);
-            }
-        }
-
-        log.debug("Called save for [{0}]", () -> cacheAttr.getCacheName());
-    }
-
-    /**
-     * Gets the size attribute of the Cache object. This return the number of elements, not the byte
-     * size.
-     * <p>
-     * @return The size value
-     */
-    @Override
-    public int getSize()
-    {
-        return memCache.getSize();
-    }
-
-    /**
-     * Gets the cacheType attribute of the Cache object.
-     * <p>
-     * @return The cacheType value
-     */
-    @Override
-    public CacheType getCacheType()
-    {
-        return CacheType.CACHE_HUB;
-    }
-
-    /**
-     * Gets the status attribute of the Cache object.
-     * <p>
-     * @return The status value
-     */
-    @Override
-    public CacheStatus getStatus()
-    {
-        return alive.get() ? CacheStatus.ALIVE : CacheStatus.DISPOSED;
-    }
-
-    /**
-     * Gets stats for debugging.
-     * <p>
-     * @return String
-     */
-    @Override
-    public String getStats()
-    {
-        return getStatistics().toString();
-    }
-
-    /**
-     * This returns data gathered for this region and all the auxiliaries it currently uses.
-     * <p>
-     * @return Statistics and Info on the Region.
-     */
-    public ICacheStats getStatistics()
-    {
-        ICacheStats stats = new CacheStats();
-        stats.setRegionName(this.getCacheName());
-
-        // store the composite cache stats first
-        ArrayList<IStatElement<?>> elems = new ArrayList<>();
-
-        elems.add(new StatElement<>("HitCountRam", Long.valueOf(getHitCountRam())));
-        elems.add(new StatElement<>("HitCountAux", Long.valueOf(getHitCountAux())));
-
-        stats.setStatElements(elems);
-
-        // memory + aux, memory is not considered an auxiliary internally
-        int total = auxCaches.length + 1;
-        ArrayList<IStats> auxStats = new ArrayList<>(total);
-
-        auxStats.add(getMemoryCache().getStatistics());
-
-        for (AuxiliaryCache<K, V> aux : auxCaches)
-        {
-            auxStats.add(aux.getStatistics());
-        }
-
-        // store the auxiliary stats
-        stats.setAuxiliaryCacheStats(auxStats);
-
-        return stats;
-    }
-
-    /**
-     * Gets the cacheName attribute of the Cache object. This is also known as the region name.
-     * <p>
-     * @return The cacheName value
-     */
-    @Override
-    public String getCacheName()
-    {
-        return cacheAttr.getCacheName();
-    }
-
-    /**
-     * Gets the default element attribute of the Cache object This returns a copy. It does not
-     * return a reference to the attributes.
-     * <p>
-     * @return The attributes value
-     */
-    public IElementAttributes getElementAttributes()
-    {
-        if (attr != null)
-        {
-            return attr.clone();
-        }
-        return null;
-    }
-
-    /**
-     * Sets the default element attribute of the Cache object.
-     * <p>
-     * @param attr
-     */
-    public void setElementAttributes(IElementAttributes attr)
-    {
-        this.attr = attr;
-    }
-
-    /**
-     * Gets the ICompositeCacheAttributes attribute of the Cache object.
-     * <p>
-     * @return The ICompositeCacheAttributes value
-     */
-    public ICompositeCacheAttributes getCacheAttributes()
-    {
-        return this.cacheAttr;
-    }
-
-    /**
-     * Sets the ICompositeCacheAttributes attribute of the Cache object.
-     * <p>
-     * @param cattr The new ICompositeCacheAttributes value
-     */
-    public void setCacheAttributes(ICompositeCacheAttributes cattr)
-    {
-        this.cacheAttr = cattr;
-        // need a better way to do this, what if it is in error
-        this.memCache.initialize(this);
-    }
-
-    /**
-     * Gets the elementAttributes attribute of the Cache object.
-     * <p>
-     * @param key
-     * @return The elementAttributes value
-     * @throws CacheException
-     * @throws IOException
-     */
-    public IElementAttributes getElementAttributes(K key)
-        throws CacheException, IOException
-    {
-        ICacheElement<K, V> ce = get(key);
-        if (ce == null)
-        {
-            throw new ObjectNotFoundException("key " + key + " is not found");
-        }
-        return ce.getElementAttributes();
-    }
-
-    /**
-     * Determine if the element is expired based on the values of the element attributes
-     *
-     * @param element the element
-     *
-     * @return true if the element is expired
-     */
-    public boolean isExpired(ICacheElement<K, V> element)
-    {
-        return isExpired(element, System.currentTimeMillis(),
-                ElementEventType.EXCEEDED_MAXLIFE_ONREQUEST,
-                ElementEventType.EXCEEDED_IDLETIME_ONREQUEST);
-    }
-
-    /**
-     * Check if the element is expired based on the values of the element attributes
-     *
-     * @param element the element
-     * @param timestamp the timestamp to compare to
-     * @param eventMaxlife the event to fire in case the max life time is exceeded
-     * @param eventIdle the event to fire in case the idle time is exceeded
-     *
-     * @return true if the element is expired
-     */
-    public boolean isExpired(ICacheElement<K, V> element, long timestamp,
-            ElementEventType eventMaxlife, ElementEventType eventIdle)
-    {
-        try
-        {
-            IElementAttributes attributes = element.getElementAttributes();
-
-            if (!attributes.getIsEternal())
-            {
-                // Remove if maxLifeSeconds exceeded
-                long maxLifeSeconds = attributes.getMaxLife();
-                long createTime = attributes.getCreateTime();
-
-                final long timeFactorForMilliseconds = attributes.getTimeFactorForMilliseconds();
-
-                if (maxLifeSeconds != -1 && (timestamp - createTime) > (maxLifeSeconds * timeFactorForMilliseconds))
-                {
-                    log.debug("Exceeded maxLife: {0}", () -> element.getKey());
-
-                    handleElementEvent(element, eventMaxlife);
-                    return true;
-                }
-                long idleTime = attributes.getIdleTime();
-                long lastAccessTime = attributes.getLastAccessTime();
-
-                // Remove if maxIdleTime exceeded
-                // If you have a 0 size memory cache, then the last access will
-                // not get updated.
-                // you will need to set the idle time to -1.
-                if ((idleTime != -1) && (timestamp - lastAccessTime) > idleTime * timeFactorForMilliseconds)
-                {
-                    log.debug("Exceeded maxIdle: {0}", () -> element.getKey());
-
-                    handleElementEvent(element, eventIdle);
-                    return true;
-                }
-            }
-        }
-        catch (Exception e)
-        {
-            log.error("Error determining expiration period, expiring", e);
-            return true;
-        }
-
-        return false;
-    }
-
-    /**
-     * If there are event handlers for the item, then create an event and queue it up.
-     * <p>
-     * This does not call handle directly; instead the handler and the event are put into a queue.
-     * This prevents the event handling from blocking normal cache operations.
-     * <p>
-     * @param element the item
-     * @param eventType the event type
-     */
-    public void handleElementEvent(ICacheElement<K, V> element, ElementEventType eventType)
-    {
-        ArrayList<IElementEventHandler> eventHandlers = element.getElementAttributes().getElementEventHandlers();
-        if (eventHandlers != null)
-        {
-            log.debug("Element Handlers are registered.  Create event type {0}", eventType);
-            if (elementEventQ == null)
-            {
-                log.warn("No element event queue available for cache {0}", getCacheName());
-                return;
-            }
-            IElementEvent<ICacheElement<K, V>> event = new ElementEvent<>(element, eventType);
-            for (IElementEventHandler hand : eventHandlers)
-            {
-                try
-                {
-                   elementEventQ.addElementEvent(hand, event);
-                }
-                catch (IOException e)
-                {
-                    log.error("Trouble adding element event to queue", e);
-                }
-            }
-        }
-    }
-
-    /**
-     * Create the MemoryCache based on the config parameters.
-     * TODO: consider making this an auxiliary, despite its close tie to the CacheHub.
-     * TODO: might want to create a memory cache config file separate from that of the hub -- ICompositeCacheAttributes
-     * <p>
-     * @param cattr
-     */
-    private void createMemoryCache(ICompositeCacheAttributes cattr)
-    {
-        if (memCache == null)
-        {
-            try
-            {
-                Class<?> c = Class.forName(cattr.getMemoryCacheName());
-                @SuppressWarnings("unchecked") // Need cast
-                IMemoryCache<K, V> newInstance = (IMemoryCache<K, V>) c.newInstance();
-                memCache = newInstance;
-                memCache.initialize(this);
-            }
-            catch (Exception e)
-            {
-                log.warn("Failed to init mem cache, using: LRUMemoryCache", e);
-
-                this.memCache = new LRUMemoryCache<>();
-                this.memCache.initialize(this);
-            }
-        }
-        else
-        {
-            log.warn("Refusing to create memory cache -- already exists.");
-        }
-    }
-
-    /**
-     * Access to the memory cache for instrumentation.
-     * <p>
-     * @return the MemoryCache implementation
-     */
-    public IMemoryCache<K, V> getMemoryCache()
-    {
-        return memCache;
-    }
-
-    /**
-     * Number of times a requested item was found in the memory cache.
-     * <p>
-     * @return number of hits in memory
-     */
-    public long getHitCountRam()
-    {
-        return hitCountRam.get();
-    }
-
-    /**
-     * Number of times a requested item was found in and auxiliary cache.
-     * @return number of auxiliary hits.
-     */
-    public long getHitCountAux()
-    {
-        return hitCountAux.get();
-    }
-
-    /**
-     * Number of times a requested element was not found.
-     * @return number of misses.
-     */
-    public long getMissCountNotFound()
-    {
-        return missCountNotFound.get();
-    }
-
-    /**
-     * Number of times a requested element was found but was expired.
-     * @return number of found but expired gets.
-     */
-    public long getMissCountExpired()
-    {
-        return missCountExpired.get();
-    }
-
-    /**
-     * @return Returns the updateCount.
-     */
-    public long getUpdateCount()
-    {
-        return updateCount.get();
-    }
-
-    /**
-     * Sets the key matcher used by get matching.
-     * <p>
-     * @param keyMatcher
-     */
-    @Override
-    public void setKeyMatcher(IKeyMatcher<K> keyMatcher)
-    {
-        if (keyMatcher != null)
-        {
-            this.keyMatcher = keyMatcher;
-        }
-    }
-
-    /**
-     * Returns the key matcher used by get matching.
-     * <p>
-     * @return keyMatcher
-     */
-    public IKeyMatcher<K> getKeyMatcher()
-    {
-        return this.keyMatcher;
-    }
-
-    /**
-     * This returns the stats.
-     * <p>
-     * @return getStats()
-     */
-    @Override
-    public String toString()
-    {
-        return getStats();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/CompositeCacheConfigurator.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/CompositeCacheConfigurator.java
deleted file mode 100644
index c132376..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/CompositeCacheConfigurator.java
+++ /dev/null
@@ -1,535 +0,0 @@
-package org.apache.commons.jcs.engine.control;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Properties;
-import java.util.StringTokenizer;
-
-import org.apache.commons.jcs.auxiliary.AuxiliaryCache;
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes;
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheConfigurator;
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheFactory;
-import org.apache.commons.jcs.engine.behavior.ICache;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheAttributes;
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.engine.behavior.IRequireScheduler;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-import org.apache.commons.jcs.engine.match.KeyMatcherPatternImpl;
-import org.apache.commons.jcs.engine.match.behavior.IKeyMatcher;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-import org.apache.commons.jcs.utils.config.OptionConverter;
-import org.apache.commons.jcs.utils.config.PropertySetter;
-
-/**
- * This class configures JCS based on a properties object.
- * <p>
- * This class is based on the log4j class org.apache.log4j.PropertyConfigurator which was made by:
- * "Luke Blanshard" &lt;Luke@quiq.com&gt;"Mark DONSZELMANN" &lt;Mark.Donszelmann@cern.ch&gt;"Anders Kristensen"
- * &lt;akristensen@dynamicsoft.com&gt;
- */
-public class CompositeCacheConfigurator
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog( CompositeCacheConfigurator.class );
-
-    /** The prefix of relevant system properties */
-    protected static final String SYSTEM_PROPERTY_KEY_PREFIX = "jcs";
-
-    /** normal region prefix */
-    protected static final String REGION_PREFIX = "jcs.region.";
-
-    /** system region prefix. might not be used */
-    protected static final String SYSTEM_REGION_PREFIX = "jcs.system.";
-
-    /** auxiliary prefix */
-    protected static final String AUXILIARY_PREFIX = "jcs.auxiliary.";
-
-    /** .attributes */
-    protected static final String ATTRIBUTE_PREFIX = ".attributes";
-
-    /** .cacheattributes */
-    protected static final String CACHE_ATTRIBUTE_PREFIX = ".cacheattributes";
-
-    /** .elementattributes */
-    protected static final String ELEMENT_ATTRIBUTE_PREFIX = ".elementattributes";
-
-    /**
-     * jcs.auxiliary.NAME.keymatcher=CLASSNAME
-     * <p>
-     * jcs.auxiliary.NAME.keymatcher.attributes.CUSTOMPROPERTY=VALUE
-     */
-    public static final String KEY_MATCHER_PREFIX = ".keymatcher";
-
-    /**
-     * Constructor for the CompositeCacheConfigurator object
-     */
-    public CompositeCacheConfigurator()
-    {
-        // empty
-    }
-
-    /**
-     * Create caches used internally. System status gives them creation priority.
-     *<p>
-     * @param props Configuration properties
-     * @param ccm Cache hub
-     */
-    protected void parseSystemRegions( Properties props, CompositeCacheManager ccm )
-    {
-        for (String key : props.stringPropertyNames() )
-        {
-            if ( key.startsWith( SYSTEM_REGION_PREFIX ) && key.indexOf( "attributes" ) == -1 )
-            {
-                String regionName = key.substring( SYSTEM_REGION_PREFIX.length() );
-                String auxiliaries = OptionConverter.findAndSubst( key, props );
-                ICache<?, ?> cache;
-                synchronized ( regionName )
-                {
-                    cache = parseRegion( props, ccm, regionName, auxiliaries, null, SYSTEM_REGION_PREFIX );
-                }
-                ccm.addCache( regionName, cache );
-            }
-        }
-    }
-
-    /**
-     * Parse region elements.
-     *<p>
-     * @param props Configuration properties
-     * @param ccm Cache hub
-     */
-    protected void parseRegions( Properties props, CompositeCacheManager ccm )
-    {
-        List<String> regionNames = new ArrayList<>();
-
-        for (String key : props.stringPropertyNames() )
-        {
-            if ( key.startsWith( REGION_PREFIX ) && key.indexOf( "attributes" ) == -1 )
-            {
-                String regionName = key.substring( REGION_PREFIX.length() );
-                regionNames.add( regionName );
-                String auxiliaries = OptionConverter.findAndSubst( key, props );
-                ICache<?, ?> cache;
-                synchronized ( regionName )
-                {
-                    cache = parseRegion( props, ccm, regionName, auxiliaries );
-                }
-                ccm.addCache( regionName, cache );
-            }
-        }
-
-        log.info( "Parsed regions {0}", regionNames );
-    }
-
-    /**
-     * Create cache region.
-     *<p>
-     * @param props Configuration properties
-     * @param ccm Cache hub
-     * @param regName Name of the cache region
-     * @param auxiliaries Comma separated list of auxiliaries
-     *
-     * @return CompositeCache
-     */
-    protected <K, V> CompositeCache<K, V> parseRegion(
-            Properties props, CompositeCacheManager ccm, String regName, String auxiliaries )
-    {
-        return parseRegion( props, ccm, regName, auxiliaries, null, REGION_PREFIX );
-    }
-
-    /**
-     * Get all the properties for a region and configure its cache.
-     * <p>
-     * This method tells the other parse method the name of the region prefix.
-     *<p>
-     * @param props Configuration properties
-     * @param ccm Cache hub
-     * @param regName Name of the cache region
-     * @param auxiliaries Comma separated list of auxiliaries
-     * @param cca Cache configuration
-     *
-     * @return CompositeCache
-     */
-    protected <K, V> CompositeCache<K, V> parseRegion(
-            Properties props, CompositeCacheManager ccm, String regName, String auxiliaries,
-            ICompositeCacheAttributes cca )
-    {
-        return parseRegion( props, ccm, regName, auxiliaries, cca, REGION_PREFIX );
-    }
-
-    /**
-     * Get all the properties for a region and configure its cache.
-     *<p>
-     * @param props Configuration properties
-     * @param ccm Cache hub
-     * @param regName Name of the cache region
-     * @param auxiliaries Comma separated list of auxiliaries
-     * @param cca Cache configuration
-     * @param regionPrefix Prefix for the region
-     *
-     * @return CompositeCache
-     */
-    protected <K, V> CompositeCache<K, V> parseRegion(
-            Properties props, CompositeCacheManager ccm, String regName, String auxiliaries,
-            ICompositeCacheAttributes cca, String regionPrefix )
-    {
-        // First, create or get the cache and element attributes, and create
-        // the cache.
-        IElementAttributes ea = parseElementAttributes( props, regName,
-                ccm.getDefaultElementAttributes(), regionPrefix );
-
-        ICompositeCacheAttributes instantiationCca = cca == null
-                ? parseCompositeCacheAttributes(props, regName, ccm.getDefaultCacheAttributes(), regionPrefix)
-                : cca;
-        CompositeCache<K, V> cache = newCache(instantiationCca, ea);
-
-        // Inject cache manager
-        cache.setCompositeCacheManager(ccm);
-
-        // Inject scheduler service
-        cache.setScheduledExecutorService(ccm.getScheduledExecutorService());
-
-        // Inject element event queue
-        cache.setElementEventQueue(ccm.getElementEventQueue());
-
-        if (cache.getMemoryCache() instanceof IRequireScheduler)
-        {
-            ((IRequireScheduler)cache.getMemoryCache()).setScheduledExecutorService(
-                    ccm.getScheduledExecutorService());
-        }
-
-        if (auxiliaries != null)
-        {
-            // Next, create the auxiliaries for the new cache
-            List<AuxiliaryCache<K, V>> auxList = new ArrayList<>();
-
-            log.debug( "Parsing region name \"{0}\", value \"{1}\"", regName, auxiliaries );
-
-            // We must skip over ',' but not white space
-            StringTokenizer st = new StringTokenizer( auxiliaries, "," );
-
-            // If value is not in the form ", appender.." or "", then we should set
-            // the priority of the category.
-
-            if ( !( auxiliaries.startsWith( "," ) || auxiliaries.equals( "" ) ) )
-            {
-                // just to be on the safe side...
-                if ( !st.hasMoreTokens() )
-                {
-                    return null;
-                }
-            }
-
-            AuxiliaryCache<K, V> auxCache;
-            String auxName;
-            while ( st.hasMoreTokens() )
-            {
-                auxName = st.nextToken().trim();
-                if ( auxName == null || auxName.equals( "," ) )
-                {
-                    continue;
-                }
-                log.debug( "Parsing auxiliary named \"{0}\".", auxName );
-
-                auxCache = parseAuxiliary( props, ccm, auxName, regName );
-
-                if ( auxCache != null )
-                {
-                    if (auxCache instanceof IRequireScheduler)
-                    {
-                        ((IRequireScheduler) auxCache).setScheduledExecutorService(
-                                ccm.getScheduledExecutorService());
-                    }
-
-                    auxList.add( auxCache );
-                }
-            }
-
-            // Associate the auxiliaries with the cache
-            @SuppressWarnings("unchecked") // No generic arrays in java
-            AuxiliaryCache<K, V>[] auxArray = auxList.toArray( new AuxiliaryCache[0] );
-            cache.setAuxCaches( auxArray );
-        }
-
-        // Return the new cache
-        return cache;
-    }
-
-    protected <K, V> CompositeCache<K, V> newCache(
-            ICompositeCacheAttributes cca, IElementAttributes ea)
-    {
-        return new CompositeCache<>( cca, ea );
-    }
-
-    /**
-     * Get an ICompositeCacheAttributes for the listed region.
-     *<p>
-     * @param props Configuration properties
-     * @param regName the region name
-     * @param defaultCCAttr the default cache attributes
-     *
-     * @return ICompositeCacheAttributes
-     */
-    protected ICompositeCacheAttributes parseCompositeCacheAttributes( Properties props,
-            String regName, ICompositeCacheAttributes defaultCCAttr )
-    {
-        return parseCompositeCacheAttributes( props, regName, defaultCCAttr, REGION_PREFIX );
-    }
-
-    /**
-     * Get the main attributes for a region.
-     *<p>
-     * @param props Configuration properties
-     * @param regName the region name
-     * @param defaultCCAttr the default cache attributes
-     * @param regionPrefix the region prefix
-     *
-     * @return ICompositeCacheAttributes
-     */
-    protected ICompositeCacheAttributes parseCompositeCacheAttributes( Properties props,
-            String regName, ICompositeCacheAttributes defaultCCAttr, String regionPrefix )
-    {
-        ICompositeCacheAttributes ccAttr;
-
-        String attrName = regionPrefix + regName + CACHE_ATTRIBUTE_PREFIX;
-
-        // auxFactory was not previously initialized.
-        // String prefix = regionPrefix + regName + ATTRIBUTE_PREFIX;
-        ccAttr = OptionConverter.instantiateByKey( props, attrName, null );
-
-        if ( ccAttr == null )
-        {
-            log.info( "No special CompositeCacheAttributes class defined for "
-                    + "key [{0}], using default class.", attrName );
-
-            ccAttr = defaultCCAttr;
-        }
-
-        log.debug( "Parsing options for \"{0}\"", attrName );
-
-        PropertySetter.setProperties( ccAttr, props, attrName + "." );
-        ccAttr.setCacheName( regName );
-
-        log.debug( "End of parsing for \"{0}\"", attrName );
-
-        // GET CACHE FROM FACTORY WITH ATTRIBUTES
-        ccAttr.setCacheName( regName );
-        return ccAttr;
-    }
-
-    /**
-     * Create the element attributes from the properties object for a cache region.
-     *<p>
-     * @param props Configuration properties
-     * @param regName the region name
-     * @param defaultEAttr the default element attributes
-     * @param regionPrefix the region prefix
-     *
-     * @return IElementAttributes
-     */
-    protected IElementAttributes parseElementAttributes( Properties props, String regName,
-            IElementAttributes defaultEAttr, String regionPrefix )
-    {
-        IElementAttributes eAttr;
-
-        String attrName = regionPrefix + regName + CompositeCacheConfigurator.ELEMENT_ATTRIBUTE_PREFIX;
-
-        // auxFactory was not previously initialized.
-        // String prefix = regionPrefix + regName + ATTRIBUTE_PREFIX;
-        eAttr = OptionConverter.instantiateByKey( props, attrName, null );
-        if ( eAttr == null )
-        {
-            log.info( "No special ElementAttribute class defined for key [{0}], "
-                    + "using default class.", attrName );
-
-            eAttr = defaultEAttr;
-        }
-
-        log.debug( "Parsing options for \"{0}\"", attrName );
-
-        PropertySetter.setProperties( eAttr, props, attrName + "." );
-        // eAttr.setCacheName( regName );
-
-        log.debug( "End of parsing for \"{0}\"", attrName );
-
-        // GET CACHE FROM FACTORY WITH ATTRIBUTES
-        // eAttr.setCacheName( regName );
-        return eAttr;
-    }
-
-    /**
-     * Get an aux cache for the listed aux for a region.
-     *<p>
-     * @param props the configuration properties
-     * @param ccm Cache hub
-     * @param auxName the name of the auxiliary cache
-     * @param regName the name of the region.
-     * @return AuxiliaryCache
-     */
-    protected <K, V> AuxiliaryCache<K, V> parseAuxiliary( Properties props, CompositeCacheManager ccm,
-            String auxName, String regName )
-    {
-        log.debug( "parseAuxiliary {0}", auxName );
-
-        // GET CACHE
-        @SuppressWarnings("unchecked") // Common map for all caches
-        AuxiliaryCache<K, V> auxCache = (AuxiliaryCache<K, V>) ccm.getAuxiliaryCache(auxName, regName);
-
-        if (auxCache == null)
-        {
-            // GET FACTORY
-            AuxiliaryCacheFactory auxFac = ccm.registryFacGet( auxName );
-            if ( auxFac == null )
-            {
-                // auxFactory was not previously initialized.
-                String prefix = AUXILIARY_PREFIX + auxName;
-                auxFac = OptionConverter.instantiateByKey( props, prefix, null );
-                if ( auxFac == null )
-                {
-                    log.error( "Could not instantiate auxFactory named \"{0}\"", auxName );
-                    return null;
-                }
-
-                auxFac.setName( auxName );
-
-                if ( auxFac instanceof IRequireScheduler)
-                {
-                	((IRequireScheduler)auxFac).setScheduledExecutorService(ccm.getScheduledExecutorService());
-                }
-
-                auxFac.initialize();
-                ccm.registryFacPut( auxFac );
-            }
-
-            // GET ATTRIBUTES
-            AuxiliaryCacheAttributes auxAttr = ccm.registryAttrGet( auxName );
-            String attrName = AUXILIARY_PREFIX + auxName + ATTRIBUTE_PREFIX;
-            if ( auxAttr == null )
-            {
-                // auxFactory was not previously initialized.
-                String prefix = AUXILIARY_PREFIX + auxName + ATTRIBUTE_PREFIX;
-                auxAttr = OptionConverter.instantiateByKey( props, prefix, null );
-                if ( auxAttr == null )
-                {
-                    log.error( "Could not instantiate auxAttr named \"{0}\"", attrName );
-                    return null;
-                }
-                auxAttr.setName( auxName );
-                ccm.registryAttrPut( auxAttr );
-            }
-
-            auxAttr = auxAttr.clone();
-
-            log.debug( "Parsing options for \"{0}\"", attrName );
-
-            PropertySetter.setProperties( auxAttr, props, attrName + "." );
-            auxAttr.setCacheName( regName );
-
-            log.debug( "End of parsing for \"{0}\"", attrName );
-
-            // GET CACHE FROM FACTORY WITH ATTRIBUTES
-            auxAttr.setCacheName( regName );
-
-            String auxPrefix = AUXILIARY_PREFIX + auxName;
-
-            // CONFIGURE THE EVENT LOGGER
-            ICacheEventLogger cacheEventLogger =
-                    AuxiliaryCacheConfigurator.parseCacheEventLogger( props, auxPrefix );
-
-            // CONFIGURE THE ELEMENT SERIALIZER
-            IElementSerializer elementSerializer =
-                    AuxiliaryCacheConfigurator.parseElementSerializer( props, auxPrefix );
-
-            // CONFIGURE THE KEYMATCHER
-            //IKeyMatcher keyMatcher = parseKeyMatcher( props, auxPrefix );
-            // TODO add to factory interface
-
-            // Consider putting the compositeCache back in the factory interface
-            // since the manager may not know about it at this point.
-            // need to make sure the manager already has the cache
-            // before the auxiliary is created.
-            try
-            {
-                auxCache = auxFac.createCache( auxAttr, ccm, cacheEventLogger, elementSerializer );
-            }
-            catch (Exception e)
-            {
-                log.error( "Could not instantiate auxiliary cache named \"{0}\"", regName, e );
-                return null;
-            }
-
-            ccm.addAuxiliaryCache(auxName, regName, auxCache);
-        }
-
-        return auxCache;
-    }
-
-    /**
-     * Any property values will be replaced with system property values that match the key.
-     * <p>
-     * @param props
-     */
-    protected static void overrideWithSystemProperties( Properties props )
-    {
-        // override any setting with values from the system properties.
-        Properties sysProps = System.getProperties();
-        for (String key : sysProps.stringPropertyNames())
-        {
-            if ( key.startsWith( SYSTEM_PROPERTY_KEY_PREFIX ) )
-            {
-                log.info( "Using system property [[{0}] [{1}]]", () -> key,
-                        () -> sysProps.getProperty( key ) );
-                props.setProperty( key, sysProps.getProperty( key ) );
-            }
-        }
-    }
-
-    /**
-     * Creates a custom key matcher if one is defined.  Else, it uses the default.
-     * <p>
-     * @param props
-     * @param auxPrefix - ex. AUXILIARY_PREFIX + auxName
-     * @return IKeyMatcher
-     */
-    protected <K> IKeyMatcher<K> parseKeyMatcher( Properties props, String auxPrefix )
-    {
-
-        // auxFactory was not previously initialized.
-        String keyMatcherClassName = auxPrefix + KEY_MATCHER_PREFIX;
-        IKeyMatcher<K> keyMatcher = OptionConverter.instantiateByKey( props, keyMatcherClassName, null );
-        if ( keyMatcher != null )
-        {
-            String attributePrefix = auxPrefix + KEY_MATCHER_PREFIX + ATTRIBUTE_PREFIX;
-            PropertySetter.setProperties( keyMatcher, props, attributePrefix + "." );
-            log.info( "Using custom key matcher [{0}] for auxiliary [{1}]", keyMatcher, auxPrefix );
-        }
-        else
-        {
-            // use the default standard serializer
-            keyMatcher = new KeyMatcherPatternImpl<>();
-            log.info( "Using standard key matcher [{0}] for auxiliary [{1}]", keyMatcher, auxPrefix );
-        }
-        return keyMatcher;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/CompositeCacheManager.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/CompositeCacheManager.java
deleted file mode 100644
index b13b66d..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/CompositeCacheManager.java
+++ /dev/null
@@ -1,927 +0,0 @@
-package org.apache.commons.jcs.engine.control;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.management.ManagementFactory;
-import java.security.AccessControlException;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Properties;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.Executors;
-import java.util.concurrent.LinkedBlockingDeque;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.stream.Collectors;
-
-import javax.management.MBeanServer;
-import javax.management.ObjectName;
-
-import org.apache.commons.jcs.access.exception.CacheException;
-import org.apache.commons.jcs.admin.JCSAdminBean;
-import org.apache.commons.jcs.auxiliary.AuxiliaryCache;
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes;
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheFactory;
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheConstants;
-import org.apache.commons.jcs.engine.CompositeCacheAttributes;
-import org.apache.commons.jcs.engine.ElementAttributes;
-import org.apache.commons.jcs.engine.behavior.ICache;
-import org.apache.commons.jcs.engine.behavior.ICacheType.CacheType;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheAttributes;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-import org.apache.commons.jcs.engine.behavior.IProvideScheduler;
-import org.apache.commons.jcs.engine.behavior.IShutdownObserver;
-import org.apache.commons.jcs.engine.control.event.ElementEventQueue;
-import org.apache.commons.jcs.engine.control.event.behavior.IElementEventQueue;
-import org.apache.commons.jcs.engine.stats.CacheStats;
-import org.apache.commons.jcs.engine.stats.behavior.ICacheStats;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-import org.apache.commons.jcs.utils.config.OptionConverter;
-import org.apache.commons.jcs.utils.threadpool.DaemonThreadFactory;
-import org.apache.commons.jcs.utils.threadpool.ThreadPoolManager;
-import org.apache.commons.jcs.utils.timing.ElapsedTimer;
-
-/**
- * Manages a composite cache. This provides access to caches and is the primary way to shutdown the
- * caching system as a whole.
- * <p>
- * The composite cache manager is responsible for creating / configuring cache regions. It serves as
- * a factory for the ComositeCache class. The CompositeCache is the core of JCS, the hub for various
- * auxiliaries.
- */
-public class CompositeCacheManager
-    implements IRemoteCacheConstants, ICompositeCacheManager, IProvideScheduler
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog( CompositeCacheManager.class );
-
-    /** JMX object name */
-    public static final String JMX_OBJECT_NAME = "org.apache.commons.jcs:type=JCSAdminBean";
-
-    /** This is the name of the config file that we will look for by default. */
-    private static final String DEFAULT_CONFIG = "/cache.ccf";
-
-    /** default region prefix */
-    private static final String DEFAULT_REGION = "jcs.default";
-
-    /** Should we use system property substitutions. */
-    private static final boolean DEFAULT_USE_SYSTEM_PROPERTIES = true;
-
-    /** Once configured, you can force a reconfiguration of sorts. */
-    private static final boolean DEFAULT_FORCE_RECONFIGURATION = false;
-
-    /** Caches managed by this cache manager */
-    private final ConcurrentMap<String, ICache<?, ?>> caches = new ConcurrentHashMap<>();
-
-    /** Number of clients accessing this cache manager */
-    private final AtomicInteger clients = new AtomicInteger(0);
-
-    /** Default cache attributes for this cache manager */
-    private ICompositeCacheAttributes defaultCacheAttr = new CompositeCacheAttributes();
-
-    /** Default element attributes for this cache manager */
-    private IElementAttributes defaultElementAttr = new ElementAttributes();
-
-    /** Used to keep track of configured auxiliaries */
-    private final ConcurrentMap<String, AuxiliaryCacheFactory> auxiliaryFactoryRegistry =
-        new ConcurrentHashMap<>( );
-
-    /** Used to keep track of attributes for auxiliaries. */
-    private final ConcurrentMap<String, AuxiliaryCacheAttributes> auxiliaryAttributeRegistry =
-        new ConcurrentHashMap<>( );
-
-    /** Used to keep track of configured auxiliaries */
-    private final ConcurrentMap<String, AuxiliaryCache<?, ?>> auxiliaryCaches =
-        new ConcurrentHashMap<>( );
-
-    /** Properties with which this manager was configured. This is exposed for other managers. */
-    private Properties configurationProperties;
-
-    /** The default auxiliary caches to be used if not preconfigured */
-    private String defaultAuxValues;
-
-    /** The Singleton Instance */
-    private static CompositeCacheManager instance;
-
-    /** Stack for those waiting for notification of a shutdown. */
-    private final LinkedBlockingDeque<IShutdownObserver> shutdownObservers = new LinkedBlockingDeque<>();
-
-    /** The central background scheduler. */
-    private ScheduledExecutorService scheduledExecutor;
-
-    /** The central event queue. */
-    private IElementEventQueue elementEventQueue;
-
-    /** Shutdown hook thread instance */
-    private Thread shutdownHook;
-
-    /** Indicates whether the instance has been initialized. */
-    private boolean isInitialized = false;
-
-    /** Indicates whether configure has been called. */
-    private boolean isConfigured = false;
-
-    /** Indicates whether JMX bean has been registered. */
-    private boolean isJMXRegistered = false;
-
-    private String jmxName = JMX_OBJECT_NAME;
-
-    /**
-     * Gets the CacheHub instance. For backward compatibility, if this creates the instance it will
-     * attempt to configure it with the default configuration. If you want to configure from your
-     * own source, use {@link #getUnconfiguredInstance}and then call {@link #configure}
-     * <p>
-     * @return CompositeCacheManager
-     * @throws CacheException if the configuration cannot be loaded
-     */
-    public static synchronized CompositeCacheManager getInstance() throws CacheException
-    {
-        return getInstance( DEFAULT_CONFIG );
-    }
-
-    /**
-     * Initializes the cache manager using the props file for the given name.
-     * <p>
-     * @param propsFilename
-     * @return CompositeCacheManager configured from the give propsFileName
-     * @throws CacheException if the configuration cannot be loaded
-     */
-    public static synchronized CompositeCacheManager getInstance( String propsFilename ) throws CacheException
-    {
-        if ( instance == null )
-        {
-            log.info( "Instance is null, creating with config [{0}]", propsFilename );
-            instance = createInstance();
-        }
-
-        if (!instance.isInitialized())
-        {
-            instance.initialize();
-        }
-
-        if (!instance.isConfigured())
-        {
-            instance.configure( propsFilename );
-        }
-
-        instance.clients.incrementAndGet();
-
-        return instance;
-    }
-
-    /**
-     * Get a CacheHub instance which is not configured. If an instance already exists, it will be
-     * returned.
-     *<p>
-     * @return CompositeCacheManager
-     */
-    public static synchronized CompositeCacheManager getUnconfiguredInstance()
-    {
-        if ( instance == null )
-        {
-            log.info( "Instance is null, returning unconfigured instance" );
-            instance = createInstance();
-        }
-
-        if (!instance.isInitialized())
-        {
-            instance.initialize();
-        }
-
-        instance.clients.incrementAndGet();
-
-        return instance;
-    }
-
-    /**
-     * Simple factory method, must override in subclasses so getInstance creates / returns the
-     * correct object.
-     * <p>
-     * @return CompositeCacheManager
-     */
-    protected static CompositeCacheManager createInstance()
-    {
-        return new CompositeCacheManager();
-    }
-
-    /**
-     * Default constructor
-     */
-    protected CompositeCacheManager()
-    {
-        // empty
-    }
-
-    /** Creates a shutdown hook and starts the scheduler service */
-    protected void initialize()
-    {
-        if (!isInitialized)
-        {
-            this.shutdownHook = new Thread(() -> {
-                if ( isInitialized() )
-                {
-                    log.info("Shutdown hook activated. Shutdown was not called. Shutting down JCS.");
-                    shutDown();
-                }
-            });
-            try
-            {
-                Runtime.getRuntime().addShutdownHook( shutdownHook );
-            }
-            catch ( AccessControlException e )
-            {
-                log.error( "Could not register shutdown hook.", e );
-            }
-
-            this.scheduledExecutor = Executors.newScheduledThreadPool(4,
-                    new DaemonThreadFactory("JCS-Scheduler-", Thread.MIN_PRIORITY));
-
-            // Register JMX bean
-            if (!isJMXRegistered && jmxName != null)
-            {
-                MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
-                JCSAdminBean adminBean = new JCSAdminBean(this);
-                try
-                {
-                    ObjectName jmxObjectName = new ObjectName(jmxName);
-                    mbs.registerMBean(adminBean, jmxObjectName);
-                    isJMXRegistered = true;
-                }
-                catch (Exception e)
-                {
-                    log.warn( "Could not register JMX bean.", e );
-                }
-            }
-
-            isInitialized = true;
-        }
-    }
-
-    /**
-     * Get the element event queue
-     *
-     * @return the elementEventQueue
-     */
-    public IElementEventQueue getElementEventQueue()
-    {
-        return elementEventQueue;
-    }
-
-    /**
-     * Get the scheduler service
-     *
-     * @return the scheduledExecutor
-     */
-    @Override
-    public ScheduledExecutorService getScheduledExecutorService()
-    {
-        return scheduledExecutor;
-    }
-
-    /**
-     * Configure with default properties file
-     * @throws CacheException if the configuration cannot be loaded
-     */
-    public void configure() throws CacheException
-    {
-        configure( DEFAULT_CONFIG );
-    }
-
-    /**
-     * Configure from specific properties file.
-     * <p>
-     * @param propFile Path <u>within classpath </u> to load configuration from
-     * @throws CacheException if the configuration cannot be loaded
-     */
-    public void configure( String propFile ) throws CacheException
-    {
-        log.info( "Creating cache manager from config file: {0}", propFile );
-
-        Properties props = new Properties();
-
-        try (InputStream is = getClass().getResourceAsStream( propFile ))
-        {
-            props.load( is );
-            log.debug( "File [{0}] contained {1} properties", () -> propFile, () -> props.size());
-        }
-        catch ( IOException ex )
-        {
-            throw new CacheException("Failed to load properties for name [" + propFile + "]", ex);
-        }
-
-        configure( props );
-    }
-
-    /**
-     * Configure from properties object.
-     * <p>
-     * This method will call configure, instructing it to use system properties as a default.
-     * @param props
-     */
-    public void configure( Properties props )
-    {
-        configure( props, DEFAULT_USE_SYSTEM_PROPERTIES );
-    }
-
-    /**
-     * Configure from properties object, overriding with values from the system properties if
-     * instructed.
-     * <p>
-     * You can override a specific value by passing in a system property:
-     * <p>
-     * For example, you could override this value in the cache.ccf file by starting up your program
-     * with the argument: -Djcs.auxiliary.LTCP.attributes.TcpListenerPort=1111
-     * <p>
-     * @param props
-     * @param useSystemProperties -- if true, values starting with jcs will be put into the props
-     *            file prior to configuring the cache.
-     */
-    public void configure( Properties props, boolean useSystemProperties )
-    {
-        configure( props, useSystemProperties, DEFAULT_FORCE_RECONFIGURATION );
-    }
-
-    /**
-     * Configure from properties object, overriding with values from the system properties if
-     * instructed.
-     * <p>
-     * You can override a specific value by passing in a system property:
-     * <p>
-     * For example, you could override this value in the cache.ccf file by starting up your program
-     * with the argument: -Djcs.auxiliary.LTCP.attributes.TcpListenerPort=1111
-     * <p>
-     * @param props
-     * @param useSystemProperties -- if true, values starting with jcs will be put into the props
-     *            file prior to configuring the cache.
-     * @param forceReconfiguration - if the manager is already configured, we will try again. This
-     *            may not work properly.
-     */
-    public synchronized void configure( Properties props, boolean useSystemProperties, boolean forceReconfiguration )
-    {
-        if ( props == null )
-        {
-            log.error( "No properties found. Please configure the cache correctly." );
-            return;
-        }
-
-        if ( isConfigured )
-        {
-            if ( !forceReconfiguration )
-            {
-                log.debug( "Configure called after the manager has been configured.  "
-                         + "Force reconfiguration is false. Doing nothing" );
-                return;
-            }
-            else
-            {
-                log.info( "Configure called after the manager has been configured.  "
-                        + "Force reconfiguration is true. Reconfiguring as best we can." );
-            }
-        }
-        if ( useSystemProperties )
-        {
-            CompositeCacheConfigurator.overrideWithSystemProperties( props );
-        }
-        doConfigure( props );
-    }
-
-    /**
-     * Configure the cache using the supplied properties.
-     * <p>
-     * @param properties assumed not null
-     */
-    private void doConfigure( Properties properties )
-    {
-        // We will expose this for managers that need raw properties.
-        this.configurationProperties = properties;
-
-        // set the props value and then configure the ThreadPoolManager
-        ThreadPoolManager.setProps( properties );
-        ThreadPoolManager poolMgr = ThreadPoolManager.getInstance();
-        log.debug( "ThreadPoolManager = {0}", poolMgr);
-
-        // Create event queue
-        this.elementEventQueue = new ElementEventQueue();
-
-        // configure the cache
-        CompositeCacheConfigurator configurator = newConfigurator();
-
-        ElapsedTimer timer = new ElapsedTimer();
-
-        // set default value list
-        this.defaultAuxValues = OptionConverter.findAndSubst( CompositeCacheManager.DEFAULT_REGION,
-                properties );
-
-        log.info( "Setting default auxiliaries to \"{0}\"", this.defaultAuxValues );
-
-        // set default cache attr
-        this.defaultCacheAttr = configurator.parseCompositeCacheAttributes( properties, "",
-                new CompositeCacheAttributes(), DEFAULT_REGION );
-
-        log.info( "setting defaultCompositeCacheAttributes to {0}", this.defaultCacheAttr );
-
-        // set default element attr
-        this.defaultElementAttr = configurator.parseElementAttributes( properties, "",
-                new ElementAttributes(), DEFAULT_REGION );
-
-        log.info( "setting defaultElementAttributes to {0}", this.defaultElementAttr );
-
-        // set up system caches to be used by non system caches
-        // need to make sure there is no circularity of reference
-        configurator.parseSystemRegions( properties, this );
-
-        // setup preconfigured caches
-        configurator.parseRegions( properties, this );
-
-        log.info( "Finished configuration in {0} ms.", () -> timer.getElapsedTime());
-
-        isConfigured = true;
-    }
-
-    /**
-     * Gets the defaultCacheAttributes attribute of the CacheHub object
-     * <p>
-     * @return The defaultCacheAttributes value
-     */
-    public ICompositeCacheAttributes getDefaultCacheAttributes()
-    {
-        return this.defaultCacheAttr.clone();
-    }
-
-    /**
-     * Gets the defaultElementAttributes attribute of the CacheHub object
-     * <p>
-     * @return The defaultElementAttributes value
-     */
-    public IElementAttributes getDefaultElementAttributes()
-    {
-        return this.defaultElementAttr.clone();
-    }
-
-    /**
-     * Gets the cache attribute of the CacheHub object
-     * <p>
-     * @param cacheName
-     * @return CompositeCache -- the cache region controller
-     */
-    @Override
-    public <K, V> CompositeCache<K, V> getCache( String cacheName )
-    {
-        return getCache( cacheName, getDefaultCacheAttributes() );
-    }
-
-    /**
-     * Gets the cache attribute of the CacheHub object
-     * <p>
-     * @param cacheName
-     * @param cattr
-     * @return CompositeCache
-     */
-    public <K, V> CompositeCache<K, V> getCache( String cacheName, ICompositeCacheAttributes cattr )
-    {
-        cattr.setCacheName( cacheName );
-        return getCache( cattr, getDefaultElementAttributes() );
-    }
-
-    /**
-     * Gets the cache attribute of the CacheHub object
-     * <p>
-     * @param cacheName
-     * @param cattr
-     * @param attr
-     * @return CompositeCache
-     */
-    public <K, V> CompositeCache<K, V>  getCache( String cacheName, ICompositeCacheAttributes cattr, IElementAttributes attr )
-    {
-        cattr.setCacheName( cacheName );
-        return getCache( cattr, attr );
-    }
-
-    /**
-     * Gets the cache attribute of the CacheHub object
-     * <p>
-     * @param cattr
-     * @return CompositeCache
-     */
-    public <K, V> CompositeCache<K, V>  getCache( ICompositeCacheAttributes cattr )
-    {
-        return getCache( cattr, getDefaultElementAttributes() );
-    }
-
-    /**
-     * If the cache has already been created, then the CacheAttributes and the element Attributes
-     * will be ignored. Currently there is no overriding the CacheAttributes once it is set up. You
-     * can change the default ElementAttributes for a region later.
-     * <p>
-     * Overriding the default elemental attributes will require changing the way the attributes are
-     * assigned to elements. Get cache creates a cache with defaults if none are specified. We might
-     * want to create separate method for creating/getting. . .
-     * <p>
-     * @param cattr
-     * @param attr
-     * @return CompositeCache
-     */
-    @SuppressWarnings("unchecked") // Need to cast because of common map for all caches
-    public <K, V> CompositeCache<K, V>  getCache( ICompositeCacheAttributes cattr, IElementAttributes attr )
-    {
-        log.debug( "attr = {0}", attr );
-
-        CompositeCache<K, V> cache = (CompositeCache<K, V>) caches.computeIfAbsent(cattr.getCacheName(),
-                cacheName -> {
-            CompositeCacheConfigurator configurator = newConfigurator();
-            return configurator.parseRegion( this.getConfigurationProperties(), this, cacheName,
-                                              this.defaultAuxValues, cattr );
-        });
-
-        return cache;
-    }
-
-    protected CompositeCacheConfigurator newConfigurator() {
-        return new CompositeCacheConfigurator();
-    }
-
-    /**
-     * @param name
-     */
-    public void freeCache( String name )
-    {
-        freeCache( name, false );
-    }
-
-    /**
-     * @param name
-     * @param fromRemote
-     */
-    public void freeCache( String name, boolean fromRemote )
-    {
-        CompositeCache<?, ?> cache = (CompositeCache<?, ?>) caches.remove( name );
-
-        if ( cache != null )
-        {
-            cache.dispose( fromRemote );
-        }
-    }
-
-    /**
-     * Calls freeCache on all regions
-     */
-    public void shutDown()
-    {
-        synchronized (CompositeCacheManager.class)
-        {
-            // shutdown element event queue
-            if (this.elementEventQueue != null)
-            {
-                this.elementEventQueue.dispose();
-            }
-
-            // shutdown all scheduled jobs
-            this.scheduledExecutor.shutdownNow();
-
-            // shutdown all thread pools
-            ThreadPoolManager.dispose();
-
-            // notify any observers
-            IShutdownObserver observer = null;
-            while ((observer = shutdownObservers.poll()) != null)
-            {
-                observer.shutdown();
-            }
-
-            // Unregister JMX bean
-            if (isJMXRegistered)
-            {
-                MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
-                try
-                {
-                    ObjectName jmxObjectName = new ObjectName(jmxName);
-                    mbs.unregisterMBean(jmxObjectName);
-                }
-                catch (Exception e)
-                {
-                    log.warn( "Could not unregister JMX bean.", e );
-                }
-
-                isJMXRegistered = false;
-            }
-
-            // do the traditional shutdown of the regions.
-            getCacheNames().forEach(this::freeCache);
-
-            // shut down auxiliaries
-            for (String key : auxiliaryCaches.keySet())
-            {
-                try
-                {
-                    freeAuxiliaryCache(key);
-                }
-                catch (IOException e)
-                {
-                    log.warn("Auxiliary cache {0} failed to shut down", key, e);
-                }
-            }
-
-            // shut down factories
-            auxiliaryFactoryRegistry.values().forEach(AuxiliaryCacheFactory::dispose);
-
-            auxiliaryAttributeRegistry.clear();
-            auxiliaryFactoryRegistry.clear();
-
-            if (shutdownHook != null)
-            {
-                try
-                {
-                    Runtime.getRuntime().removeShutdownHook(shutdownHook);
-                }
-                catch (IllegalStateException e)
-                {
-                    // May fail if the JVM is already shutting down
-                }
-
-                this.shutdownHook = null;
-            }
-
-            isConfigured = false;
-            isInitialized = false;
-        }
-    }
-
-    /** */
-    public void release()
-    {
-        release( false );
-    }
-
-    /**
-     * @param fromRemote
-     */
-    private void release( boolean fromRemote )
-    {
-        synchronized ( CompositeCacheManager.class )
-        {
-            // Wait until called by the last client
-            if ( clients.decrementAndGet() > 0 )
-            {
-                log.debug( "Release called, but {0} remain", clients);
-                return;
-            }
-
-            log.debug( "Last client called release. There are {0} caches which will be disposed",
-                    () -> caches.size());
-
-            caches.values().stream()
-                .filter(cache -> cache != null)
-                .forEach(cache -> {
-                    ((CompositeCache<?, ?>)cache).dispose( fromRemote );
-                });
-        }
-    }
-
-    /**
-     * Returns a list of the current cache names.
-     * @return Set<String>
-     */
-    public Set<String> getCacheNames()
-    {
-        return caches.keySet();
-    }
-
-    /**
-     * @return ICacheType.CACHE_HUB
-     */
-    public CacheType getCacheType()
-    {
-        return CacheType.CACHE_HUB;
-    }
-
-    /**
-     * @param auxFac
-     */
-    public void registryFacPut( AuxiliaryCacheFactory auxFac )
-    {
-        auxiliaryFactoryRegistry.put( auxFac.getName(), auxFac );
-    }
-
-    /**
-     * @param name
-     * @return AuxiliaryCacheFactory
-     */
-    public AuxiliaryCacheFactory registryFacGet( String name )
-    {
-        return auxiliaryFactoryRegistry.get( name );
-    }
-
-    /**
-     * @param auxAttr
-     */
-    public void registryAttrPut( AuxiliaryCacheAttributes auxAttr )
-    {
-        auxiliaryAttributeRegistry.put( auxAttr.getName(), auxAttr );
-    }
-
-    /**
-     * @param name
-     * @return AuxiliaryCacheAttributes
-     */
-    public AuxiliaryCacheAttributes registryAttrGet( String name )
-    {
-        return auxiliaryAttributeRegistry.get( name );
-    }
-
-    /**
-     * Add a cache to the map of registered caches
-     *
-     * @param cacheName the region name
-     * @param cache the cache instance
-     */
-    public void addCache(String cacheName, ICache<?, ?> cache)
-    {
-        caches.put(cacheName, cache);
-    }
-
-    /**
-     * Add a cache to the map of registered auxiliary caches
-     *
-     * @param auxName the auxiliary name
-     * @param cacheName the region name
-     * @param cache the cache instance
-     */
-    public void addAuxiliaryCache(String auxName, String cacheName, AuxiliaryCache<?, ?> cache)
-    {
-        String key = String.format("aux.%s.region.%s", auxName, cacheName);
-        auxiliaryCaches.put(key, cache);
-    }
-
-    /**
-     * Get a cache from the map of registered auxiliary caches
-     *
-     * @param auxName the auxiliary name
-     * @param cacheName the region name
-     *
-     * @return the cache instance
-     */
-    @Override
-    @SuppressWarnings("unchecked") // because of common map for all auxiliary caches
-    public <K, V> AuxiliaryCache<K, V> getAuxiliaryCache(String auxName, String cacheName)
-    {
-        String key = String.format("aux.%s.region.%s", auxName, cacheName);
-        return (AuxiliaryCache<K, V>) auxiliaryCaches.get(key);
-    }
-
-    /**
-     * Dispose a cache and remove it from the map of registered auxiliary caches
-     *
-     * @param auxName the auxiliary name
-     * @param cacheName the region name
-     * @throws IOException if disposing of the cache fails
-     */
-    public void freeAuxiliaryCache(String auxName, String cacheName) throws IOException
-    {
-        String key = String.format("aux.%s.region.%s", auxName, cacheName);
-        freeAuxiliaryCache(key);
-    }
-
-    /**
-     * Dispose a cache and remove it from the map of registered auxiliary caches
-     *
-     * @param key the key into the map of auxiliaries
-     * @throws IOException if disposing of the cache fails
-     */
-    public void freeAuxiliaryCache(String key) throws IOException
-    {
-        AuxiliaryCache<?, ?> aux = auxiliaryCaches.remove( key );
-
-        if ( aux != null )
-        {
-            aux.dispose();
-        }
-    }
-
-    /**
-     * Gets stats for debugging. This calls gets statistics and then puts all the results in a
-     * string. This returns data for all regions.
-     * <p>
-     * @return String
-     */
-    @Override
-    public String getStats()
-    {
-        ICacheStats[] stats = getStatistics();
-        if ( stats == null )
-        {
-            return "NONE";
-        }
-
-        // force the array elements into a string.
-        StringBuilder buf = new StringBuilder();
-        Arrays.stream(stats).forEach(stat -> {
-            buf.append( "\n---------------------------\n" );
-            buf.append( stat );
-        });
-        return buf.toString();
-    }
-
-    /**
-     * This returns data gathered for all regions and all the auxiliaries they currently uses.
-     * <p>
-     * @return ICacheStats[]
-     */
-    public ICacheStats[] getStatistics()
-    {
-        List<ICacheStats> cacheStats = caches.values().stream()
-            .filter(cache -> cache != null)
-            .map(cache -> ((CompositeCache<?, ?>)cache).getStatistics() )
-            .collect(Collectors.toList());
-
-        return cacheStats.toArray( new CacheStats[0] );
-    }
-
-    /**
-     * Perhaps the composite cache itself should be the observable object. It doesn't make much of a
-     * difference. There are some problems with region by region shutdown. Some auxiliaries are
-     * global. They will need to track when every region has shutdown before doing things like
-     * closing the socket with a lateral.
-     * <p>
-     * @param observer
-     */
-    @Override
-    public void registerShutdownObserver( IShutdownObserver observer )
-    {
-    	if (!shutdownObservers.contains(observer))
-    	{
-    		shutdownObservers.push( observer );
-    	}
-    	else
-    	{
-    		log.warn("Shutdown observer added twice {0}", observer);
-    	}
-    }
-
-    /**
-     * @param observer
-     */
-    @Override
-    public void deregisterShutdownObserver( IShutdownObserver observer )
-    {
-        shutdownObservers.remove( observer );
-    }
-
-    /**
-     * This is exposed so other manager can get access to the props.
-     * <p>
-     * @return the configurationProperties
-     */
-    @Override
-    public Properties getConfigurationProperties()
-    {
-        return configurationProperties;
-    }
-
-    /**
-     * @return the isInitialized
-     */
-    public boolean isInitialized()
-    {
-        return isInitialized;
-    }
-
-    /**
-     * @return the isConfigured
-     */
-    public boolean isConfigured()
-    {
-        return isConfigured;
-    }
-
-    public void setJmxName(final String name)
-    {
-        if (isJMXRegistered)
-        {
-            throw new IllegalStateException("Too late, MBean registration is done");
-        }
-        jmxName = name;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/event/ElementEvent.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/event/ElementEvent.java
deleted file mode 100644
index e25a603..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/event/ElementEvent.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package org.apache.commons.jcs.engine.control.event;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.EventObject;
-
-import org.apache.commons.jcs.engine.control.event.behavior.ElementEventType;
-import org.apache.commons.jcs.engine.control.event.behavior.IElementEvent;
-
-/**
- * Element events will trigger the creation of Element Event objects. This is a wrapper around the
- * cache element that indicates the event triggered.
- */
-public class ElementEvent<T>
-    extends EventObject
-    implements IElementEvent<T>
-{
-    /** Don't change */
-    private static final long serialVersionUID = -5364117411457467056L;
-
-    /** default event code */
-    private ElementEventType elementEvent = ElementEventType.EXCEEDED_MAXLIFE_BACKGROUND;
-
-    /**
-     * Constructor for the ElementEvent object
-     * <p>
-     * @param source The Cache Element
-     * @param elementEvent The event id defined in the enum class.
-     */
-    public ElementEvent( T source, ElementEventType elementEvent )
-    {
-        super( source );
-        this.elementEvent = elementEvent;
-    }
-
-    /**
-     * Gets the elementEvent attribute of the ElementEvent object
-     * <p>
-     * @return The elementEvent value. The List of values is defined in ElementEventType.
-     */
-    @Override
-    public ElementEventType getElementEvent()
-    {
-        return elementEvent;
-    }
-
-    /**
-     * @return the source of the event.
-     */
-    @SuppressWarnings("unchecked") // Generified
-    @Override
-    public T getSource()
-    {
-        return (T) super.getSource();
-
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/event/ElementEventQueue.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/event/ElementEventQueue.java
deleted file mode 100644
index d86843f..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/event/ElementEventQueue.java
+++ /dev/null
@@ -1,184 +0,0 @@
-package org.apache.commons.jcs.engine.control.event;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.util.concurrent.ExecutorService;
-
-import org.apache.commons.jcs.engine.control.event.behavior.IElementEvent;
-import org.apache.commons.jcs.engine.control.event.behavior.IElementEventHandler;
-import org.apache.commons.jcs.engine.control.event.behavior.IElementEventQueue;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-import org.apache.commons.jcs.utils.threadpool.PoolConfiguration;
-import org.apache.commons.jcs.utils.threadpool.PoolConfiguration.WhenBlockedPolicy;
-import org.apache.commons.jcs.utils.threadpool.ThreadPoolManager;
-
-/**
- * An event queue is used to propagate ordered cache events to one and only one target listener.
- */
-public class ElementEventQueue
-    implements IElementEventQueue
-{
-    private static final String THREAD_PREFIX = "JCS-ElementEventQueue-";
-
-    /** The logger */
-    private static final Log log = LogManager.getLog( ElementEventQueue.class );
-
-    /** shutdown or not */
-    private boolean destroyed = false;
-
-    /** The worker thread pool. */
-    private ExecutorService queueProcessor;
-
-    /**
-     * Constructor for the ElementEventQueue object
-     */
-    public ElementEventQueue()
-    {
-        queueProcessor = ThreadPoolManager.getInstance().createPool(
-        		new PoolConfiguration(false, 0, 1, 1, 0, WhenBlockedPolicy.RUN, 1), THREAD_PREFIX);
-
-        log.debug( "Constructed: {0}", this );
-    }
-
-    /**
-     * Dispose queue
-     */
-    @Override
-    public void dispose()
-    {
-        if ( !destroyed )
-        {
-            destroyed = true;
-
-            // synchronize on queue so the thread will not wait forever,
-            // and then interrupt the QueueProcessor
-            queueProcessor.shutdownNow();
-            queueProcessor = null;
-
-            log.info( "Element event queue destroyed: {0}", this );
-        }
-    }
-
-    /**
-     * Adds an ElementEvent to be handled
-     * @param hand The IElementEventHandler
-     * @param event The IElementEventHandler IElementEvent event
-     * @throws IOException
-     */
-    @Override
-    public <T> void addElementEvent( IElementEventHandler hand, IElementEvent<T> event )
-        throws IOException
-    {
-
-        log.debug( "Adding Event Handler to QUEUE, !destroyed = {0}", !destroyed );
-
-        if (destroyed)
-        {
-            log.warn("Event submitted to disposed element event queue {0}", event);
-        }
-        else
-        {
-            ElementEventRunner runner = new ElementEventRunner( hand, event );
-
-            log.debug( "runner = {0}", runner );
-
-            queueProcessor.execute(runner);
-        }
-    }
-
-    // /////////////////////////// Inner classes /////////////////////////////
-
-    /**
-     * Retries before declaring failure.
-     */
-    protected abstract class AbstractElementEventRunner
-        implements Runnable
-    {
-        /**
-         * Main processing method for the AbstractElementEvent object
-         */
-        @SuppressWarnings("synthetic-access")
-        @Override
-        public void run()
-        {
-            try
-            {
-                doRun();
-                // happy and done.
-            }
-            catch ( IOException e )
-            {
-                // Too bad. The handler has problems.
-                log.warn( "Giving up element event handling {0}", ElementEventQueue.this, e );
-            }
-        }
-
-        /**
-         * This will do the work or trigger the work to be done.
-         * <p>
-         * @throws IOException
-         */
-        protected abstract void doRun()
-            throws IOException;
-    }
-
-    /**
-     * ElementEventRunner.
-     */
-    private class ElementEventRunner
-        extends AbstractElementEventRunner
-    {
-        /** the handler */
-        private final IElementEventHandler hand;
-
-        /** event */
-        private final IElementEvent<?> event;
-
-        /**
-         * Constructor for the PutEvent object.
-         * <p>
-         * @param hand
-         * @param event
-         * @throws IOException
-         */
-        @SuppressWarnings("synthetic-access")
-        ElementEventRunner( IElementEventHandler hand, IElementEvent<?> event )
-            throws IOException
-        {
-            log.debug( "Constructing {0}", this );
-            this.hand = hand;
-            this.event = event;
-        }
-
-        /**
-         * Tells the handler to handle the event.
-         * <p>
-         * @throws IOException
-         */
-        @Override
-        protected void doRun()
-            throws IOException
-        {
-            hand.handleElementEvent( event );
-        }
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/event/behavior/ElementEventType.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/event/behavior/ElementEventType.java
deleted file mode 100644
index 52b0fe8..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/event/behavior/ElementEventType.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package org.apache.commons.jcs.engine.control.event.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * This describes the events that an item can encounter.
- */
-public enum ElementEventType
-{
-    /** Background expiration */
-    EXCEEDED_MAXLIFE_BACKGROUND,
-
-    /*** Expiration discovered on request */
-    EXCEEDED_MAXLIFE_ONREQUEST,
-
-    /** Background expiration */
-    EXCEEDED_IDLETIME_BACKGROUND,
-
-    /** Expiration discovered on request */
-    EXCEEDED_IDLETIME_ONREQUEST,
-
-    /** Moving from memory to disk (what if no disk?) */
-    SPOOLED_DISK_AVAILABLE,
-
-    /** Moving from memory to disk (what if no disk?) */
-    SPOOLED_DISK_NOT_AVAILABLE,
-
-    /** Moving from memory to disk, but item is not spoolable */
-    SPOOLED_NOT_ALLOWED //,
-
-    /** Removed actively by a remove command. (Could distinguish between local and remote) */
-    //REMOVED,
-    /**
-     * Element was requested from cache. Not sure we ever want to implement this.
-     */
-    //GET
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/event/behavior/IElementEvent.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/event/behavior/IElementEvent.java
deleted file mode 100644
index 74b9c7f..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/event/behavior/IElementEvent.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package org.apache.commons.jcs.engine.control.event.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.Serializable;
-
-/**
- * Defines how an element event object should behave.
- */
-public interface IElementEvent<T>
-    extends Serializable
-{
-    /**
-     * Gets the elementEvent attribute of the IElementEvent object. This code is Contained in the
-     * IElememtEventConstants class.
-     *<p>
-     * @return The elementEvent value
-     */
-    ElementEventType getElementEvent();
-
-    /**
-     * @return the source of the event.
-     */
-    T getSource();
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/event/behavior/IElementEventHandler.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/event/behavior/IElementEventHandler.java
deleted file mode 100644
index 3aed8b7..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/event/behavior/IElementEventHandler.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package org.apache.commons.jcs.engine.control.event.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * This interface defines the behavior for event handler. Event handlers are
- * transient. They are not replicated and are not written to disk.
- * <p>
- * If you want an event handler by default for all elements in a region, then
- * you can add it to the default element attributes. This way it will get created
- * whenever an item gets put into the cache.
- *
- */
-public interface IElementEventHandler
-{
-    /**
-     * Handle events for this element. The events are typed.
-     *
-     * @param event
-     *            The event created by the cache.
-     */
-    <T> void handleElementEvent( IElementEvent<T> event );
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/event/behavior/IElementEventQueue.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/event/behavior/IElementEventQueue.java
deleted file mode 100644
index fc5f84e..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/event/behavior/IElementEventQueue.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package org.apache.commons.jcs.engine.control.event.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-
-/**
- * Interface for an element event queue. An event queue is used to propagate
- * ordered element events in one region.
- *
- */
-public interface IElementEventQueue
-{
-    /**
-     * Adds an ElementEvent to be handled
-     *
-     * @param hand
-     *            The IElementEventHandler
-     * @param event
-     *            The IElementEventHandler IElementEvent event
-     * @throws IOException
-     */
-    <T> void addElementEvent( IElementEventHandler hand, IElementEvent<T> event )
-        throws IOException;
-
-    /**
-     * Destroy the event queue
-     *
-     */
-    void dispose();
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/group/GroupAttrName.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/group/GroupAttrName.java
deleted file mode 100644
index 8da08eb..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/group/GroupAttrName.java
+++ /dev/null
@@ -1,117 +0,0 @@
-package org.apache.commons.jcs.engine.control.group;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.Serializable;
-
-/**
- * Description of the Class
- */
-public class GroupAttrName<T>
-    implements Serializable
-{
-    /** Don't change */
-    private static final long serialVersionUID = 1586079686300744198L;
-
-    /** Description of the Field */
-    public final GroupId groupId;
-
-    /** the name of the attribute */
-    public final T attrName;
-
-    /** Cached toString value */
-    private String toString;
-
-    /**
-     * Constructor for the GroupAttrName object
-     * @param groupId
-     * @param attrName
-     */
-    public GroupAttrName( GroupId groupId, T attrName )
-    {
-        this.groupId = groupId;
-        this.attrName = attrName;
-
-        if ( groupId == null )
-        {
-            throw new IllegalArgumentException( "groupId must not be null." );
-        }
-    }
-
-    /**
-     * Tests object equality.
-     * @param obj The <code>GroupAttrName</code> instance to test.
-     * @return Whether equal.
-     */
-    @Override
-    public boolean equals( Object obj )
-    {
-        if ( obj == null || !( obj instanceof GroupAttrName ) )
-        {
-            return false;
-        }
-        GroupAttrName<?> to = (GroupAttrName<?>) obj;
-
-        if (groupId.equals( to.groupId ))
-        {
-            if (attrName == null && to.attrName == null)
-            {
-                return true;
-            }
-            else if (attrName == null || to.attrName == null)
-            {
-                return false;
-            }
-
-            return  attrName.equals( to.attrName );
-        }
-
-        return false;
-    }
-
-    /**
-     * @return A hash code based on the hash code of @ #groupid} and {@link #attrName}.
-     */
-    @Override
-    public int hashCode()
-    {
-        if (attrName == null)
-        {
-            return groupId.hashCode();
-        }
-
-        return groupId.hashCode() ^ attrName.hashCode();
-    }
-
-    /**
-     * @return the cached value.
-     */
-    @Override
-    public String toString()
-    {
-        if ( toString == null )
-        {
-            toString = "[GAN: groupId=" + groupId + ", attrName=" + attrName + "]";
-        }
-
-        return toString;
-    }
-
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/group/GroupId.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/group/GroupId.java
deleted file mode 100644
index 5bdd83a..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/group/GroupId.java
+++ /dev/null
@@ -1,103 +0,0 @@
-package org.apache.commons.jcs.engine.control.group;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.Serializable;
-
-/**
- * Used to avoid name conflict when group cache items are mixed with non-group cache items in the
- * same cache.
- */
-public class GroupId
-    implements Serializable
-{
-    /** Don't change. */
-    private static final long serialVersionUID = 4626368486444860133L;
-
-    /** Description of the Field */
-    public final String groupName;
-
-    /** the name of the region. */
-    public final String cacheName;
-
-    /** Cached toString value. */
-    private String toString;
-
-    /**
-     * Constructor for the GroupId object
-     * <p>
-     * @param cacheName
-     * @param groupName
-     */
-    public GroupId( String cacheName, String groupName )
-    {
-        this.cacheName = cacheName;
-        this.groupName = groupName;
-
-        if ( cacheName == null )
-        {
-            throw new IllegalArgumentException( "cacheName must not be null." );
-        }
-        if ( groupName == null )
-        {
-            throw new IllegalArgumentException( "groupName must not be null." );
-        }
-    }
-
-    /**
-     * @param obj
-     * @return cacheName.equals( g.cacheName ) &amp;&amp;groupName.equals( g.groupName );
-     */
-    @Override
-    public boolean equals( Object obj )
-    {
-        if ( obj == null || !( obj instanceof GroupId ) )
-        {
-            return false;
-        }
-        GroupId g = (GroupId) obj;
-        return cacheName.equals( g.cacheName ) && groupName.equals( g.groupName );
-    }
-
-    /**
-     * @return cacheName.hashCode() + groupName.hashCode();
-     */
-    @Override
-    public int hashCode()
-    {
-        return cacheName.hashCode() + groupName.hashCode();
-    }
-
-    /**
-     * Caches the value.
-     * <p>
-     * @return debugging string.
-     */
-    @Override
-    public String toString()
-    {
-        if ( toString == null )
-        {
-            toString = "[groupId=" + cacheName + ", " + groupName + ']';
-        }
-
-        return toString;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/logging/CacheEvent.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/logging/CacheEvent.java
deleted file mode 100644
index c07ecac..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/logging/CacheEvent.java
+++ /dev/null
@@ -1,177 +0,0 @@
-package org.apache.commons.jcs.engine.logging;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEvent;
-
-import java.util.Date;
-
-/** It's returned from create and passed into log. */
-public class CacheEvent<K>
-    implements ICacheEvent<K>
-{
-    /** Don't change. */
-    private static final long serialVersionUID = -5913139566421714330L;
-
-    /** The time at which this object was created. */
-    private final long createTime = System.currentTimeMillis();
-
-    /** The auxiliary or other source of the event. */
-    private String source;
-
-    /** The cache region */
-    private String region;
-
-    /** The event name: update, get, remove, etc. */
-    private String eventName;
-
-    /** disk location, ip, etc. */
-    private String optionalDetails;
-
-    /** The key that was put or retrieved. */
-    private K key;
-
-    /**
-     * @param source the source to set
-     */
-    @Override
-	public void setSource( String source )
-    {
-        this.source = source;
-    }
-
-    /**
-     * @return the source
-     */
-    @Override
-	public String getSource()
-    {
-        return source;
-    }
-
-    /**
-     * @param region the region to set
-     */
-    @Override
-	public void setRegion( String region )
-    {
-        this.region = region;
-    }
-
-    /**
-     * @return the region
-     */
-    @Override
-	public String getRegion()
-    {
-        return region;
-    }
-
-    /**
-     * @param eventName the eventName to set
-     */
-    @Override
-	public void setEventName( String eventName )
-    {
-        this.eventName = eventName;
-    }
-
-    /**
-     * @return the eventName
-     */
-    @Override
-	public String getEventName()
-    {
-        return eventName;
-    }
-
-    /**
-     * @param optionalDetails the optionalDetails to set
-     */
-    @Override
-	public void setOptionalDetails( String optionalDetails )
-    {
-        this.optionalDetails = optionalDetails;
-    }
-
-    /**
-     * @return the optionalDetails
-     */
-    @Override
-	public String getOptionalDetails()
-    {
-        return optionalDetails;
-    }
-
-    /**
-     * @param key the key to set
-     */
-    @Override
-	public void setKey( K key )
-    {
-        this.key = key;
-    }
-
-    /**
-     * @return the key
-     */
-    @Override
-	public K getKey()
-    {
-        return key;
-    }
-
-    /**
-     * The time at which this object was created.
-     * <p>
-     * @return the createTime
-     */
-    public long getCreateTime()
-    {
-        return createTime;
-    }
-
-    /**
-     * @return reflection toString
-     */
-    @Override
-    public String toString()
-    {
-    	StringBuilder sb = new StringBuilder();
-    	sb.append("CacheEvent: ").append(eventName).append(" Created: ").append(new Date(createTime));
-    	if (source != null)
-    	{
-        	sb.append(" Source: ").append(source);
-    	}
-    	if (region != null)
-    	{
-        	sb.append(" Region: ").append(region);
-    	}
-    	if (key != null)
-    	{
-        	sb.append(" Key: ").append(key);
-    	}
-    	if (optionalDetails != null)
-    	{
-        	sb.append(" Details: ").append(optionalDetails);
-    	}
-        return sb.toString();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/logging/CacheEventLoggerDebugLogger.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/logging/CacheEventLoggerDebugLogger.java
deleted file mode 100644
index 4a2d56b..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/logging/CacheEventLoggerDebugLogger.java
+++ /dev/null
@@ -1,104 +0,0 @@
-package org.apache.commons.jcs.engine.logging;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEvent;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * This implementation simple logs to a logger at debug level, for all events. It's mainly
- * for testing. It isn't very useful otherwise.
- */
-public class CacheEventLoggerDebugLogger
-    implements ICacheEventLogger
-{
-    /** This is the name of the category. */
-    private String logCategoryName = CacheEventLoggerDebugLogger.class.getName();
-
-    /** The logger. This is recreated on set logCategoryName */
-    private Log log = LogManager.getLog( logCategoryName );
-
-    /**
-     * @param source
-     * @param region
-     * @param eventName
-     * @param optionalDetails
-     * @param key
-     * @return ICacheEvent
-     */
-    @Override
-    public <T> ICacheEvent<T> createICacheEvent( String source, String region, String eventName,
-            String optionalDetails, T key )
-    {
-        ICacheEvent<T> event = new CacheEvent<>();
-        event.setSource( source );
-        event.setRegion( region );
-        event.setEventName( eventName );
-        event.setOptionalDetails( optionalDetails );
-        event.setKey( key );
-
-        return event;
-    }
-
-    /**
-     * @param source
-     * @param eventName
-     * @param optionalDetails
-     */
-    @Override
-    public void logApplicationEvent( String source, String eventName, String optionalDetails )
-    {
-        log.debug( "{0} | {1} | {2}", source, eventName, optionalDetails );
-    }
-
-    /**
-     * @param source
-     * @param eventName
-     * @param errorMessage
-     */
-    @Override
-    public void logError( String source, String eventName, String errorMessage )
-    {
-        log.debug( "{0} | {1} | {2}", source, eventName, errorMessage );
-    }
-
-    /**
-     * @param event
-     */
-    @Override
-    public <T> void logICacheEvent( ICacheEvent<T> event )
-    {
-        log.debug( event );
-    }
-
-    /**
-     * @param logCategoryName
-     */
-    public synchronized void setLogCategoryName( String logCategoryName )
-    {
-        if ( logCategoryName != null && !logCategoryName.equals( this.logCategoryName ) )
-        {
-            this.logCategoryName = logCategoryName;
-            log = LogManager.getLog( logCategoryName );
-        }
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/logging/behavior/ICacheEvent.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/logging/behavior/ICacheEvent.java
deleted file mode 100644
index 82238f2..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/logging/behavior/ICacheEvent.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package org.apache.commons.jcs.engine.logging.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.Serializable;
-
-/** Defines the common fields required by a cache event. */
-public interface ICacheEvent<K>
-    extends Serializable
-{
-    /**
-     * @param source the source to set
-     */
-    void setSource( String source );
-
-    /**
-     * @return the source
-     */
-    String getSource();
-
-    /**
-     * @param region the region to set
-     */
-    void setRegion( String region );
-
-    /**
-     * @return the region
-     */
-    String getRegion();
-
-    /**
-     * @param eventName the eventName to set
-     */
-    void setEventName( String eventName );
-
-    /**
-     * @return the eventName
-     */
-    String getEventName();
-
-    /**
-     * @param optionalDetails the optionalDetails to set
-     */
-    void setOptionalDetails( String optionalDetails );
-
-    /**
-     * @return the optionalDetails
-     */
-    String getOptionalDetails();
-
-    /**
-     * @param key the key to set
-     */
-    void setKey( K key );
-
-    /**
-     * @return the key
-     */
-    K getKey();
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/logging/behavior/ICacheEventLogger.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/logging/behavior/ICacheEventLogger.java
deleted file mode 100644
index 7b08c67..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/logging/behavior/ICacheEventLogger.java
+++ /dev/null
@@ -1,92 +0,0 @@
-package org.apache.commons.jcs.engine.logging.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * This defines the behavior for event logging. Auxiliaries will send events to injected event
- * loggers.
- * <p>
- * In general all ICache interface methods should call the logger if one is configured. This will be
- * done on an ad hoc basis for now. Various auxiliaries may have additional events.
- */
-public interface ICacheEventLogger
-{
-	// TODO: Use enum
-    /** ICache update */
-    String UPDATE_EVENT = "update";
-
-    /** ICache get */
-    String GET_EVENT = "get";
-
-    /** ICache getMultiple */
-    String GETMULTIPLE_EVENT = "getMultiple";
-
-    /** ICache getMatching */
-    String GETMATCHING_EVENT = "getMatching";
-
-    /** ICache remove */
-    String REMOVE_EVENT = "remove";
-
-    /** ICache removeAll */
-    String REMOVEALL_EVENT = "removeAll";
-
-    /** ICache dispose */
-    String DISPOSE_EVENT = "dispose";
-
-    /** ICache enqueue. The time in the queue. */
-    //String ENQUEUE_EVENT = "enqueue";
-    /**
-     * Creates an event.
-     * <p>
-     * @param source - e.g. RemoteCacheServer
-     * @param region - the name of the region
-     * @param eventName - e.g. update, get, put, remove
-     * @param optionalDetails - any extra message
-     * @param key - the cache key
-     * @return ICacheEvent
-     */
-    <T> ICacheEvent<T> createICacheEvent( String source, String region,
-            String eventName, String optionalDetails, T key );
-
-    /**
-     * Logs an event.
-     * <p>
-     * @param event - the event created in createICacheEvent
-     */
-    <T> void logICacheEvent( ICacheEvent<T> event );
-
-    /**
-     * Logs an event. These are internal application events that do not correspond to ICache calls.
-     * <p>
-     * @param source - e.g. RemoteCacheServer
-     * @param eventName - e.g. update, get, put, remove
-     * @param optionalDetails - any extra message
-     */
-    void logApplicationEvent( String source, String eventName, String optionalDetails );
-
-    /**
-     * Logs an error.
-     * <p>
-     * @param source - e.g. RemoteCacheServer
-     * @param eventName - e.g. update, get, put, remove
-     * @param errorMessage - any error message
-     */
-    void logError( String source, String eventName, String errorMessage );
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/match/KeyMatcherPatternImpl.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/match/KeyMatcherPatternImpl.java
deleted file mode 100644
index 5b935d7..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/match/KeyMatcherPatternImpl.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package org.apache.commons.jcs.engine.match;
-
-import java.util.Set;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.match.behavior.IKeyMatcher;
-
-/** This implementation of the KeyMatcher uses standard Java Pattern matching. */
-public class KeyMatcherPatternImpl<K>
-    implements IKeyMatcher<K>
-{
-    /** Serial version */
-    private static final long serialVersionUID = 6667352064144381264L;
-
-    /**
-     * Creates a pattern and find matches on the array.
-     * <p>
-     * @param pattern
-     * @param keyArray
-     * @return Set of the matching keys
-     */
-    @Override
-    public Set<K> getMatchingKeysFromArray( String pattern, Set<K> keyArray )
-    {
-        Pattern compiledPattern = Pattern.compile( pattern );
-
-        return keyArray.stream()
-                .filter(key -> compiledPattern.matcher(key.toString()).matches())
-                .collect(Collectors.toSet());
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/match/behavior/IKeyMatcher.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/match/behavior/IKeyMatcher.java
deleted file mode 100644
index 74e9d01..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/match/behavior/IKeyMatcher.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package org.apache.commons.jcs.engine.match.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.Serializable;
-import java.util.Set;
-
-/** Key matchers need to implement this interface. */
-public interface IKeyMatcher<K> extends Serializable
-{
-    /**
-     * Creates a pattern and find matches on the array.
-     * <p>
-     * @param pattern
-     * @param keyArray
-     * @return Set of the matching keys
-     */
-    Set<K> getMatchingKeysFromArray( String pattern, Set<K> keyArray );
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/AbstractDoubleLinkedListMemoryCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/AbstractDoubleLinkedListMemoryCache.java
deleted file mode 100644
index c28103b..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/AbstractDoubleLinkedListMemoryCache.java
+++ /dev/null
@@ -1,526 +0,0 @@
-package org.apache.commons.jcs.engine.memory;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.util.List;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.control.CompositeCache;
-import org.apache.commons.jcs.engine.control.group.GroupAttrName;
-import org.apache.commons.jcs.engine.memory.util.MemoryElementDescriptor;
-import org.apache.commons.jcs.engine.stats.StatElement;
-import org.apache.commons.jcs.engine.stats.behavior.IStatElement;
-import org.apache.commons.jcs.engine.stats.behavior.IStats;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-import org.apache.commons.jcs.utils.struct.DoubleLinkedList;
-
-/**
- * This class contains methods that are common to memory caches using the double linked list, such
- * as the LRU, MRU, FIFO, and LIFO caches.
- * <p>
- * Children can control the expiration algorithm by controlling the update and get. The last item in the list will be the one
- * removed when the list fills. For instance LRU should more items to the front as they are used. FIFO should simply add new items
- * to the front of the list.
- */
-public abstract class AbstractDoubleLinkedListMemoryCache<K, V> extends AbstractMemoryCache<K, V>
-{
-    /** The logger. */
-    private static final Log log = LogManager.getLog(AbstractDoubleLinkedListMemoryCache.class);
-
-    /** thread-safe double linked list for lru */
-    protected DoubleLinkedList<MemoryElementDescriptor<K, V>> list; // TODO privatise
-
-    /**
-     * For post reflection creation initialization.
-     * <p>
-     *
-     * @param hub
-     */
-    @Override
-    public void initialize(CompositeCache<K, V> hub)
-    {
-        super.initialize(hub);
-        list = new DoubleLinkedList<>();
-        log.info("initialized MemoryCache for {0}", () -> getCacheName());
-    }
-
-    /**
-     * This is called by super initialize.
-     *
-     * NOTE: should return a thread safe map
-     *
-     * <p>
-     *
-     * @return new ConcurrentHashMap()
-     */
-    @Override
-    public ConcurrentMap<K, MemoryElementDescriptor<K, V>> createMap()
-    {
-        return new ConcurrentHashMap<>();
-    }
-
-    /**
-     * Calls the abstract method updateList.
-     * <p>
-     * If the max size is reached, an element will be put to disk.
-     * <p>
-     *
-     * @param ce
-     *            The cache element, or entry wrapper
-     * @throws IOException
-     */
-    @Override
-    public final void update(ICacheElement<K, V> ce) throws IOException
-    {
-        putCnt.incrementAndGet();
-
-        lock.lock();
-        try
-        {
-            MemoryElementDescriptor<K, V> newNode = adjustListForUpdate(ce);
-
-            // this should be synchronized if we were not using a ConcurrentHashMap
-            final K key = newNode.getCacheElement().getKey();
-            MemoryElementDescriptor<K, V> oldNode = map.put(key, newNode);
-
-            // If the node was the same as an existing node, remove it.
-            if (oldNode != null && key.equals(oldNode.getCacheElement().getKey()))
-            {
-                list.remove(oldNode);
-            }
-        }
-        finally
-        {
-            lock.unlock();
-        }
-
-        // If we are over the max spool some
-        spoolIfNeeded();
-    }
-
-    /**
-     * Children implement this to control the cache expiration algorithm
-     * <p>
-     *
-     * @param ce
-     * @return MemoryElementDescriptor the new node
-     * @throws IOException
-     */
-    protected abstract MemoryElementDescriptor<K, V> adjustListForUpdate(ICacheElement<K, V> ce) throws IOException;
-
-    /**
-     * If the max size has been reached, spool.
-     * <p>
-     *
-     * @throws Error
-     */
-    private void spoolIfNeeded() throws Error
-    {
-        int size = map.size();
-        // If the element limit is reached, we need to spool
-
-        if (size <= this.getCacheAttributes().getMaxObjects())
-        {
-            return;
-        }
-
-        log.debug("In memory limit reached, spooling");
-
-        // Write the last 'chunkSize' items to disk.
-        int chunkSizeCorrected = Math.min(size, chunkSize);
-
-        log.debug("About to spool to disk cache, map size: {0}, max objects: {1}, "
-                + "maximum items to spool: {2}", () -> size,
-                () -> this.getCacheAttributes().getMaxObjects(),
-                () -> chunkSizeCorrected);
-
-        // The spool will put them in a disk event queue, so there is no
-        // need to pre-queue the queuing. This would be a bit wasteful
-        // and wouldn't save much time in this synchronous call.
-        lock.lock();
-
-        try
-        {
-            for (int i = 0; i < chunkSizeCorrected; i++)
-            {
-                ICacheElement<K, V> lastElement = spoolLastElement();
-                if (lastElement == null)
-                {
-                    break;
-                }
-            }
-
-            // If this is out of the sync block it can detect a mismatch
-            // where there is none.
-            if (log.isDebugEnabled() && map.size() != list.size())
-            {
-                log.debug("update: After spool, size mismatch: map.size() = {0}, "
-                        + "linked list size = {1}", map.size(), list.size());
-            }
-        }
-        finally
-        {
-            lock.unlock();
-        }
-
-        log.debug("update: After spool map size: {0} linked list size = {1}",
-                () -> map.size(), () -> list.size());
-    }
-
-    /**
-     * This instructs the memory cache to remove the <i>numberToFree</i> according to its eviction
-     * policy. For example, the LRUMemoryCache will remove the <i>numberToFree</i> least recently
-     * used items. These will be spooled to disk if a disk auxiliary is available.
-     * <p>
-     *
-     * @param numberToFree
-     * @return the number that were removed. if you ask to free 5, but there are only 3, you will
-     *         get 3.
-     * @throws IOException
-     */
-    @Override
-    public int freeElements(int numberToFree) throws IOException
-    {
-        int freed = 0;
-
-        lock.lock();
-
-        try
-        {
-            for (; freed < numberToFree; freed++)
-            {
-                ICacheElement<K, V> element = spoolLastElement();
-                if (element == null)
-                {
-                    break;
-                }
-            }
-        }
-        finally
-        {
-            lock.unlock();
-        }
-
-        return freed;
-    }
-
-    /**
-     * This spools the last element in the LRU, if one exists.
-     * <p>
-     *
-     * @return ICacheElement&lt;K, V&gt; if there was a last element, else null.
-     * @throws Error
-     */
-    private ICacheElement<K, V> spoolLastElement() throws Error
-    {
-        ICacheElement<K, V> toSpool = null;
-
-        final MemoryElementDescriptor<K, V> last = list.getLast();
-        if (last != null)
-        {
-            toSpool = last.getCacheElement();
-            if (toSpool != null)
-            {
-                getCompositeCache().spoolToDisk(toSpool);
-                if (map.remove(toSpool.getKey()) == null)
-                {
-                    log.warn("update: remove failed for key: {0}", toSpool.getKey());
-
-                    if (log.isTraceEnabled())
-                    {
-                        verifyCache();
-                    }
-                }
-            }
-            else
-            {
-                throw new Error("update: last.ce is null!");
-            }
-
-            list.remove(last);
-        }
-
-        return toSpool;
-    }
-
-    /**
-     * @see org.apache.commons.jcs.engine.memory.AbstractMemoryCache#get(java.lang.Object)
-     */
-    @Override
-    public ICacheElement<K, V> get(K key) throws IOException
-    {
-        ICacheElement<K, V> ce = super.get(key);
-
-        if (log.isTraceEnabled())
-        {
-            verifyCache();
-        }
-
-        return ce;
-    }
-
-    /**
-     * Adjust the list as needed for a get. This allows children to control the algorithm
-     * <p>
-     *
-     * @param me
-     */
-    protected abstract void adjustListForGet(MemoryElementDescriptor<K, V> me);
-
-    /**
-     * Update control structures after get
-     * (guarded by the lock)
-     *
-     * @param me the memory element descriptor
-     */
-    @Override
-    protected void lockedGetElement(MemoryElementDescriptor<K, V> me)
-    {
-        adjustListForGet(me);
-    }
-
-    /**
-     * Remove element from control structure
-     * (guarded by the lock)
-     *
-     * @param me the memory element descriptor
-     */
-    @Override
-    protected void lockedRemoveElement(MemoryElementDescriptor<K, V> me)
-    {
-        list.remove(me);
-    }
-
-    /**
-     * Removes all cached items from the cache control structures.
-     * (guarded by the lock)
-     */
-    @Override
-    protected void lockedRemoveAll()
-    {
-        list.removeAll();
-    }
-
-    // --------------------------- internal methods (linked list implementation)
-    /**
-     * Adds a new node to the start of the link list.
-     * <p>
-     *
-     * @param ce
-     *            The feature to be added to the First
-     * @return MemoryElementDescriptor
-     */
-    protected MemoryElementDescriptor<K, V> addFirst(ICacheElement<K, V> ce)
-    {
-        lock.lock();
-        try
-        {
-            MemoryElementDescriptor<K, V> me = new MemoryElementDescriptor<>(ce);
-            list.addFirst(me);
-            if ( log.isTraceEnabled() )
-            {
-                verifyCache(ce.getKey());
-            }
-            return me;
-        }
-        finally
-        {
-            lock.unlock();
-        }
-    }
-
-    /**
-     * Adds a new node to the end of the link list.
-     * <p>
-     *
-     * @param ce
-     *            The feature to be added to the First
-     * @return MemoryElementDescriptor
-     */
-    protected MemoryElementDescriptor<K, V> addLast(ICacheElement<K, V> ce)
-    {
-        lock.lock();
-        try
-        {
-            MemoryElementDescriptor<K, V> me = new MemoryElementDescriptor<>(ce);
-            list.addLast(me);
-            if ( log.isTraceEnabled() )
-            {
-                verifyCache(ce.getKey());
-            }
-            return me;
-        }
-        finally
-        {
-            lock.unlock();
-        }
-    }
-
-    // ---------------------------------------------------------- debug methods
-
-    /**
-     * Dump the cache entries from first to list for debugging.
-     */
-    @SuppressWarnings("unchecked")
-    // No generics for public fields
-    private void dumpCacheEntries()
-    {
-        log.trace("dumpingCacheEntries");
-        for (MemoryElementDescriptor<K, V> me = list.getFirst(); me != null; me = (MemoryElementDescriptor<K, V>) me.next)
-        {
-            log.trace("dumpCacheEntries> key={0}, val={1}",
-                    me.getCacheElement().getKey(), me.getCacheElement().getVal());
-        }
-    }
-
-    /**
-     * Checks to see if all the items that should be in the cache are. Checks consistency between
-     * List and map.
-     */
-    @SuppressWarnings("unchecked")
-    // No generics for public fields
-    private void verifyCache()
-    {
-        boolean found = false;
-        log.trace("verifycache[{0}]: map contains {1} elements, linked list "
-                + "contains {2} elements", getCacheName(), map.size(),
-                list.size());
-        log.trace("verifycache: checking linked list by key ");
-        for (MemoryElementDescriptor<K, V> li = list.getFirst(); li != null; li = (MemoryElementDescriptor<K, V>) li.next)
-        {
-            K key = li.getCacheElement().getKey();
-            if (!map.containsKey(key))
-            {
-                log.error("verifycache[{0}]: map does not contain key : {1}",
-                        getCacheName(), key);
-                log.error("key class={0}", key.getClass());
-                log.error("key hashcode={0}", key.hashCode());
-                log.error("key toString={0}", key.toString());
-                if (key instanceof GroupAttrName)
-                {
-                    GroupAttrName<?> name = (GroupAttrName<?>) key;
-                    log.error("GroupID hashcode={0}", name.groupId.hashCode());
-                    log.error("GroupID.class={0}", name.groupId.getClass());
-                    log.error("AttrName hashcode={0}", name.attrName.hashCode());
-                    log.error("AttrName.class={0}", name.attrName.getClass());
-                }
-                dumpMap();
-            }
-            else if (map.get(key) == null)
-            {
-                log.error("verifycache[{0}]: linked list retrieval returned "
-                        + "null for key: {1}", getCacheName(), key);
-            }
-        }
-
-        log.trace("verifycache: checking linked list by value ");
-        for (MemoryElementDescriptor<K, V> li = list.getFirst(); li != null; li = (MemoryElementDescriptor<K, V>) li.next)
-        {
-            if (!map.containsValue(li))
-            {
-                log.error("verifycache[{0}]: map does not contain value: {1}",
-                        getCacheName(), li);
-                dumpMap();
-            }
-        }
-
-        log.trace("verifycache: checking via keysets!");
-        for (Object val : map.keySet())
-        {
-            found = false;
-
-            for (MemoryElementDescriptor<K, V> li = list.getFirst(); li != null; li = (MemoryElementDescriptor<K, V>) li.next)
-            {
-                if (val.equals(li.getCacheElement().getKey()))
-                {
-                    found = true;
-                    break;
-                }
-            }
-            if (!found)
-            {
-                log.error("verifycache[{0}]: key not found in list : {1}",
-                        getCacheName(), val);
-                dumpCacheEntries();
-                if (map.containsKey(val))
-                {
-                    log.error("verifycache: map contains key");
-                }
-                else
-                {
-                    log.error("verifycache: map does NOT contain key, what the HECK!");
-                }
-            }
-        }
-    }
-
-    /**
-     * Logs an error if an element that should be in the cache is not.
-     * <p>
-     *
-     * @param key
-     */
-    @SuppressWarnings("unchecked")
-    // No generics for public fields
-    private void verifyCache(K key)
-    {
-        boolean found = false;
-
-        // go through the linked list looking for the key
-        for (MemoryElementDescriptor<K, V> li = list.getFirst(); li != null; li = (MemoryElementDescriptor<K, V>) li.next)
-        {
-            if (li.getCacheElement().getKey() == key)
-            {
-                found = true;
-                log.trace("verifycache(key) key match: {0}", key);
-                break;
-            }
-        }
-        if (!found)
-        {
-            log.error("verifycache(key)[{0}], couldn't find key! : {1}",
-                    getCacheName(), key);
-        }
-    }
-
-    /**
-     * This returns semi-structured information on the memory cache, such as the size, put count,
-     * hit count, and miss count.
-     * <p>
-     *
-     * @see org.apache.commons.jcs.engine.memory.behavior.IMemoryCache#getStatistics()
-     */
-    @Override
-    public IStats getStatistics()
-    {
-        IStats stats = super.getStatistics();
-        stats.setTypeName( /* add algorithm name */"Memory Cache");
-
-        List<IStatElement<?>> elems = stats.getStatElements();
-
-        elems.add(new StatElement<>("List Size", Integer.valueOf(list.size())));
-
-        return stats;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/AbstractMemoryCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/AbstractMemoryCache.java
deleted file mode 100644
index e0b1bdb..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/AbstractMemoryCache.java
+++ /dev/null
@@ -1,513 +0,0 @@
-package org.apache.commons.jcs.engine.memory;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedHashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.stream.Collectors;
-
-import org.apache.commons.jcs.engine.behavior.ICache;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheAttributes;
-import org.apache.commons.jcs.engine.control.CompositeCache;
-import org.apache.commons.jcs.engine.control.group.GroupAttrName;
-import org.apache.commons.jcs.engine.control.group.GroupId;
-import org.apache.commons.jcs.engine.memory.behavior.IMemoryCache;
-import org.apache.commons.jcs.engine.memory.util.MemoryElementDescriptor;
-import org.apache.commons.jcs.engine.stats.StatElement;
-import org.apache.commons.jcs.engine.stats.Stats;
-import org.apache.commons.jcs.engine.stats.behavior.IStatElement;
-import org.apache.commons.jcs.engine.stats.behavior.IStats;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * This base includes some common code for memory caches.
- */
-public abstract class AbstractMemoryCache<K, V>
-    implements IMemoryCache<K, V>
-{
-    /** Log instance */
-    private static final Log log = LogManager.getLog( AbstractMemoryCache.class );
-
-    /** Cache Attributes.  Regions settings. */
-    private ICompositeCacheAttributes cacheAttributes;
-
-    /** The cache region this store is associated with */
-    private CompositeCache<K, V> cache;
-
-    /** How many to spool at a time. */
-    protected int chunkSize;
-
-    protected final Lock lock = new ReentrantLock();
-
-    /** Map where items are stored by key.  This is created by the concrete child class. */
-    protected Map<K, MemoryElementDescriptor<K, V>> map;// TODO privatise
-
-    /** number of hits */
-    protected AtomicLong hitCnt;
-
-    /** number of misses */
-    protected AtomicLong missCnt;
-
-    /** number of puts */
-    protected AtomicLong putCnt;
-
-    /**
-     * For post reflection creation initialization
-     * <p>
-     * @param hub
-     */
-    @Override
-    public void initialize( CompositeCache<K, V> hub )
-    {
-        hitCnt = new AtomicLong(0);
-        missCnt = new AtomicLong(0);
-        putCnt = new AtomicLong(0);
-
-        this.cacheAttributes = hub.getCacheAttributes();
-        this.chunkSize = cacheAttributes.getSpoolChunkSize();
-        this.cache = hub;
-
-        this.map = createMap();
-    }
-
-    /**
-     * Children must implement this method. A FIFO implementation may use a tree map. An LRU might
-     * use a hashtable. The map returned should be threadsafe.
-     * <p>
-     * @return a threadsafe Map
-     */
-    public abstract Map<K, MemoryElementDescriptor<K, V>> createMap();
-
-    /**
-     * Gets multiple items from the cache based on the given set of keys.
-     * <p>
-     * @param keys
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache for any of these keys
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMultiple(Set<K> keys)
-        throws IOException
-    {
-        if (keys != null)
-        {
-            return keys.stream()
-                .map(key -> {
-                    try
-                    {
-                        return get(key);
-                    }
-                    catch (IOException e)
-                    {
-                        return null;
-                    }
-                })
-                .filter(element -> element != null)
-                .collect(Collectors.toMap(
-                        element -> element.getKey(),
-                        element -> element));
-        }
-
-        return new HashMap<>();
-    }
-
-    /**
-     * Get an item from the cache without affecting its last access time or position. Not all memory
-     * cache implementations can get quietly.
-     * <p>
-     * @param key Identifies item to find
-     * @return Element matching key if found, or null
-     * @throws IOException
-     */
-    @Override
-    public ICacheElement<K, V> getQuiet( K key )
-        throws IOException
-    {
-        ICacheElement<K, V> ce = null;
-
-        MemoryElementDescriptor<K, V> me = map.get( key );
-        if ( me != null )
-        {
-            log.debug( "{0}: MemoryCache quiet hit for {1}",
-                    () -> getCacheName(), () -> key );
-
-            ce = me.getCacheElement();
-        }
-        else
-        {
-            log.debug( "{0}: MemoryCache quiet miss for {1}",
-                    () -> getCacheName(), () -> key );
-        }
-
-        return ce;
-    }
-
-    /**
-     * Puts an item to the cache.
-     * <p>
-     * @param ce Description of the Parameter
-     * @throws IOException Description of the Exception
-     */
-    @Override
-    public abstract void update( ICacheElement<K, V> ce )
-        throws IOException;
-
-    /**
-     * Removes all cached items from the cache.
-     * <p>
-     * @throws IOException
-     */
-    @Override
-    public void removeAll() throws IOException
-    {
-        lock.lock();
-        try
-        {
-            lockedRemoveAll();
-            map.clear();
-        }
-        finally
-        {
-            lock.unlock();
-        }
-    }
-
-    /**
-     * Removes all cached items from the cache control structures.
-     * (guarded by the lock)
-     */
-    protected abstract void lockedRemoveAll();
-
-    /**
-     * Prepares for shutdown. Reset statistics
-     * <p>
-     * @throws IOException
-     */
-    @Override
-    public void dispose()
-        throws IOException
-    {
-        removeAll();
-        hitCnt.set(0);
-        missCnt.set(0);
-        putCnt.set(0);
-        log.info( "Memory Cache dispose called." );
-    }
-
-    /**
-     * @return statistics about the cache
-     */
-    @Override
-    public IStats getStatistics()
-    {
-        IStats stats = new Stats();
-        stats.setTypeName( "Abstract Memory Cache" );
-
-        ArrayList<IStatElement<?>> elems = new ArrayList<>();
-        stats.setStatElements(elems);
-
-        elems.add(new StatElement<>("Put Count", putCnt));
-        elems.add(new StatElement<>("Hit Count", hitCnt));
-        elems.add(new StatElement<>("Miss Count", missCnt));
-        elems.add(new StatElement<>( "Map Size", Integer.valueOf(getSize()) ) );
-
-        return stats;
-    }
-
-    /**
-     * Returns the current cache size.
-     * <p>
-     * @return The size value
-     */
-    @Override
-    public int getSize()
-    {
-        return this.map.size();
-    }
-
-    /**
-     * Returns the cache (aka "region") name.
-     * <p>
-     * @return The cacheName value
-     */
-    public String getCacheName()
-    {
-        String attributeCacheName = this.cacheAttributes.getCacheName();
-        if(attributeCacheName != null)
-        {
-            return attributeCacheName;
-        }
-        return cache.getCacheName();
-    }
-
-    /**
-     * Puts an item to the cache.
-     * <p>
-     * @param ce the item
-     */
-    @Override
-    public void waterfal( ICacheElement<K, V> ce )
-    {
-        this.cache.spoolToDisk( ce );
-    }
-
-    // ---------------------------------------------------------- debug method
-    /**
-     * Dump the cache map for debugging.
-     */
-    public void dumpMap()
-    {
-        if (log.isTraceEnabled())
-        {
-            log.trace("dumpingMap");
-            map.entrySet().forEach(e ->
-                log.trace("dumpMap> key={0}, val={1}", e.getKey(),
-                        e.getValue().getCacheElement().getVal()));
-        }
-    }
-
-    /**
-     * Returns the CacheAttributes.
-     * <p>
-     * @return The CacheAttributes value
-     */
-    @Override
-    public ICompositeCacheAttributes getCacheAttributes()
-    {
-        return this.cacheAttributes;
-    }
-
-    /**
-     * Sets the CacheAttributes.
-     * <p>
-     * @param cattr The new CacheAttributes value
-     */
-    @Override
-    public void setCacheAttributes( ICompositeCacheAttributes cattr )
-    {
-        this.cacheAttributes = cattr;
-    }
-
-    /**
-     * Gets the cache hub / region that the MemoryCache is used by
-     * <p>
-     * @return The cache value
-     */
-    @Override
-    public CompositeCache<K, V> getCompositeCache()
-    {
-        return this.cache;
-    }
-
-    /**
-     * Remove all keys of the same group hierarchy.
-     * @param key the key
-     * @return true if something has been removed
-     */
-    protected boolean removeByGroup(K key)
-    {
-        GroupId groupId = ((GroupAttrName<?>) key).groupId;
-
-        // remove all keys of the same group hierarchy.
-        return map.entrySet().removeIf(entry -> {
-            K k = entry.getKey();
-
-            if (k instanceof GroupAttrName && ((GroupAttrName<?>) k).groupId.equals(groupId))
-            {
-                lock.lock();
-                try
-                {
-                    lockedRemoveElement(entry.getValue());
-                    return true;
-                }
-                finally
-                {
-                    lock.unlock();
-                }
-            }
-
-            return false;
-        });
-    }
-
-    /**
-     * Remove all keys of the same name hierarchy.
-     *
-     * @param key the key
-     * @return true if something has been removed
-     */
-    protected boolean removeByHierarchy(K key)
-    {
-        String keyString = key.toString();
-
-        // remove all keys of the same name hierarchy.
-        return map.entrySet().removeIf(entry -> {
-            K k = entry.getKey();
-
-            if (k instanceof String && ((String) k).startsWith(keyString))
-            {
-                lock.lock();
-                try
-                {
-                    lockedRemoveElement(entry.getValue());
-                    return true;
-                }
-                finally
-                {
-                    lock.unlock();
-                }
-            }
-
-            return false;
-        });
-    }
-
-    /**
-     * Remove element from control structure
-     * (guarded by the lock)
-     *
-     * @param me the memory element descriptor
-     */
-    protected abstract void lockedRemoveElement(MemoryElementDescriptor<K, V> me);
-
-    /**
-     * Removes an item from the cache. This method handles hierarchical removal. If the key is a
-     * String and ends with the CacheConstants.NAME_COMPONENT_DELIMITER, then all items with keys
-     * starting with the argument String will be removed.
-     * <p>
-     *
-     * @param key
-     * @return true if the removal was successful
-     * @throws IOException
-     */
-    @Override
-    public boolean remove(K key) throws IOException
-    {
-        log.debug("removing item for key: {0}", key);
-
-        boolean removed = false;
-
-        // handle partial removal
-        if (key instanceof String && ((String) key).endsWith(ICache.NAME_COMPONENT_DELIMITER))
-        {
-            removed = removeByHierarchy(key);
-        }
-        else if (key instanceof GroupAttrName && ((GroupAttrName<?>) key).attrName == null)
-        {
-            removed = removeByGroup(key);
-        }
-        else
-        {
-            // remove single item.
-            lock.lock();
-            try
-            {
-                MemoryElementDescriptor<K, V> me = map.remove(key);
-                if (me != null)
-                {
-                    lockedRemoveElement(me);
-                    removed = true;
-                }
-            }
-            finally
-            {
-                lock.unlock();
-            }
-        }
-
-        return removed;
-    }
-
-    /**
-     * Get an Array of the keys for all elements in the memory cache
-     *
-     * @return An Object[]
-     */
-    @Override
-    public Set<K> getKeySet()
-    {
-        return new LinkedHashSet<>(map.keySet());
-    }
-
-    /**
-     * Get an item from the cache.
-     * <p>
-     *
-     * @param key Identifies item to find
-     * @return ICacheElement&lt;K, V&gt; if found, else null
-     * @throws IOException
-     */
-    @Override
-    public ICacheElement<K, V> get(K key) throws IOException
-    {
-        ICacheElement<K, V> ce = null;
-
-        log.debug("{0}: getting item for key {1}", () -> getCacheName(),
-                () -> key);
-
-        MemoryElementDescriptor<K, V> me = map.get(key);
-
-        if (me != null)
-        {
-            hitCnt.incrementAndGet();
-            ce = me.getCacheElement();
-
-            lock.lock();
-            try
-            {
-                lockedGetElement(me);
-            }
-            finally
-            {
-                lock.unlock();
-            }
-
-            log.debug("{0}: MemoryCache hit for {1}", () -> getCacheName(),
-                    () -> key);
-        }
-        else
-        {
-            missCnt.incrementAndGet();
-
-            log.debug("{0}: MemoryCache miss for {1}", () -> getCacheName(),
-                    () -> key);
-        }
-
-        return ce;
-    }
-
-    /**
-     * Update control structures after get
-     * (guarded by the lock)
-     *
-     * @param me the memory element descriptor
-     */
-    protected abstract void lockedGetElement(MemoryElementDescriptor<K, V> me);
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/behavior/IMemoryCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/behavior/IMemoryCache.java
deleted file mode 100644
index 3ac2f4b..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/behavior/IMemoryCache.java
+++ /dev/null
@@ -1,187 +0,0 @@
-package org.apache.commons.jcs.engine.memory.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheAttributes;
-import org.apache.commons.jcs.engine.control.CompositeCache;
-import org.apache.commons.jcs.engine.stats.behavior.IStats;
-
-import java.io.IOException;
-import java.util.Map;
-import java.util.Set;
-
-/** For the framework. Insures methods a MemoryCache needs to access. */
-public interface IMemoryCache<K, V>
-{
-    /**
-     * Initialize the memory cache
-     * <p>
-     * @param cache The cache (region) this memory store is attached to.
-     */
-    void initialize( CompositeCache<K, V> cache );
-
-    /**
-     * Destroy the memory cache
-     * <p>
-     * @throws IOException
-     */
-    void dispose()
-        throws IOException;
-
-    /**
-     * Get the number of elements contained in the memory store
-     * <p>
-     * @return Element count
-     */
-    int getSize();
-
-    /**
-     * Returns the historical and statistical data for a region's memory cache.
-     * <p>
-     * @return Statistics and Info for the Memory Cache.
-     */
-    IStats getStatistics();
-
-    /**
-     * Get a set of the keys for all elements in the memory cache.
-     * <p>
-     * @return a set of the key type
-     * TODO This should probably be done in chunks with a range passed in. This
-     *       will be a problem if someone puts a 1,000,000 or so items in a
-     *       region.
-     */
-    Set<K> getKeySet();
-
-    /**
-     * Removes an item from the cache
-     * <p>
-     * @param key
-     *            Identifies item to be removed
-     * @return Description of the Return Value
-     * @throws IOException
-     *                Description of the Exception
-     */
-    boolean remove( K key )
-        throws IOException;
-
-    /**
-     * Removes all cached items from the cache.
-     * <p>
-     * @throws IOException
-     *                Description of the Exception
-     */
-    void removeAll()
-        throws IOException;
-
-    /**
-     * This instructs the memory cache to remove the <i>numberToFree</i>
-     * according to its eviction policy. For example, the LRUMemoryCache will
-     * remove the <i>numberToFree</i> least recently used items. These will be
-     * spooled to disk if a disk auxiliary is available.
-     * <p>
-     * @param numberToFree
-     * @return the number that were removed. if you ask to free 5, but there are
-     *         only 3, you will get 3.
-     * @throws IOException
-     */
-    int freeElements( int numberToFree )
-        throws IOException;
-
-    /**
-     * Get an item from the cache
-     * <p>
-     * @param key
-     *            Description of the Parameter
-     * @return Description of the Return Value
-     * @throws IOException
-     *                Description of the Exception
-     */
-    ICacheElement<K, V> get( K key )
-        throws IOException;
-
-    /**
-     * Gets multiple items from the cache based on the given set of keys.
-     * <p>
-     * @param keys
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map
-     * if there is no data in cache for any of these keys
-     * @throws IOException
-     */
-    Map<K, ICacheElement<K, V>> getMultiple( Set<K> keys )
-        throws IOException;
-
-    /**
-     * Get an item from the cache without effecting its order or last access
-     * time
-     * <p>
-     * @param key
-     *            Description of the Parameter
-     * @return The quiet value
-     * @throws IOException
-     *                Description of the Exception
-     */
-    ICacheElement<K, V> getQuiet( K key )
-        throws IOException;
-
-    /**
-     * Spools the item contained in the provided element to disk
-     * <p>
-     * @param ce
-     *            Description of the Parameter
-     * @throws IOException
-     *                Description of the Exception
-     */
-    void waterfal( ICacheElement<K, V> ce )
-        throws IOException;
-
-    /**
-     * Puts an item to the cache.
-     * <p>
-     * @param ce
-     *            Description of the Parameter
-     * @throws IOException
-     *                Description of the Exception
-     */
-    void update( ICacheElement<K, V> ce )
-        throws IOException;
-
-    /**
-     * Returns the CacheAttributes for the region.
-     * <p>
-     * @return The cacheAttributes value
-     */
-    ICompositeCacheAttributes getCacheAttributes();
-
-    /**
-     * Sets the CacheAttributes of the region.
-     * <p>
-     * @param cattr
-     *            The new cacheAttributes value
-     */
-    void setCacheAttributes( ICompositeCacheAttributes cattr );
-
-    /**
-     * Gets the cache hub / region that uses the MemoryCache.
-     * <p>
-     * @return The cache value
-     */
-    CompositeCache<K, V> getCompositeCache();
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/fifo/FIFOMemoryCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/fifo/FIFOMemoryCache.java
deleted file mode 100644
index 00f48f9..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/fifo/FIFOMemoryCache.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package org.apache.commons.jcs.engine.memory.fifo;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.memory.AbstractDoubleLinkedListMemoryCache;
-import org.apache.commons.jcs.engine.memory.util.MemoryElementDescriptor;
-
-/**
- * The items are spooled in the order they are added. No adjustments to the list are made on get.
- */
-public class FIFOMemoryCache<K, V>
-    extends AbstractDoubleLinkedListMemoryCache<K, V>
-{
-    /**
-     * Puts an item to the cache. Removes any pre-existing entries of the same key from the linked
-     * list and adds this one first.
-     * <p>
-     * @param ce The cache element, or entry wrapper
-     * @return MemoryElementDescriptor the new node
-     * @throws IOException
-     */
-    @Override
-    protected MemoryElementDescriptor<K, V> adjustListForUpdate( ICacheElement<K, V> ce )
-        throws IOException
-    {
-        return addFirst( ce );
-    }
-
-    /**
-     * Does nothing.
-     * <p>
-     * @param me
-     */
-    @Override
-    protected void adjustListForGet( MemoryElementDescriptor<K, V> me )
-    {
-        // DO NOTHING
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/lru/LHMLRUMemoryCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/lru/LHMLRUMemoryCache.java
deleted file mode 100644
index 51f8df5..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/lru/LHMLRUMemoryCache.java
+++ /dev/null
@@ -1,202 +0,0 @@
-package org.apache.commons.jcs.engine.memory.lru;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.Map;
-
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.control.CompositeCache;
-import org.apache.commons.jcs.engine.memory.AbstractMemoryCache;
-import org.apache.commons.jcs.engine.memory.util.MemoryElementDescriptor;
-import org.apache.commons.jcs.engine.stats.behavior.IStats;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * This is a test memory manager using the jdk1.4 LinkedHashMap.
- */
-public class LHMLRUMemoryCache<K, V>
-    extends AbstractMemoryCache<K, V>
-{
-    /** The Logger. */
-    private static final Log log = LogManager.getLog( LRUMemoryCache.class );
-
-    /**
-     * For post reflection creation initialization
-     * <p>
-     * @param hub
-     */
-    @Override
-    public void initialize( CompositeCache<K, V> hub )
-    {
-        super.initialize( hub );
-        log.info( "initialized LHMLRUMemoryCache for {0}", () -> getCacheName() );
-    }
-
-    /**
-     * Returns a synchronized LHMSpooler
-     * <p>
-     * @return Collections.synchronizedMap( new LHMSpooler() )
-     */
-    @Override
-    public Map<K, MemoryElementDescriptor<K, V>> createMap()
-    {
-        return Collections.synchronizedMap( new LHMSpooler() );
-    }
-
-    /**
-     * Puts an item to the cache.
-     * <p>
-     * @param ce Description of the Parameter
-     * @throws IOException
-     */
-    @Override
-    public void update( ICacheElement<K, V> ce )
-        throws IOException
-    {
-        putCnt.incrementAndGet();
-        map.put( ce.getKey(), new MemoryElementDescriptor<>(ce) );
-    }
-
-    /**
-     * Update control structures after get
-     * (guarded by the lock)
-     *
-     * @param me the memory element descriptor
-     */
-    @Override
-    protected void lockedGetElement(MemoryElementDescriptor<K, V> me)
-    {
-        // empty
-    }
-
-    /**
-     * Remove element from control structure
-     * (guarded by the lock)
-     *
-     * @param me the memory element descriptor
-     */
-    @Override
-    protected void lockedRemoveElement(MemoryElementDescriptor<K, V> me)
-    {
-        // empty
-    }
-
-    /**
-     * Removes all cached items from the cache control structures.
-     * (guarded by the lock)
-     */
-    @Override
-    protected void lockedRemoveAll()
-    {
-        // empty
-    }
-
-    /**
-     * This returns semi-structured information on the memory cache, such as the size, put count,
-     * hit count, and miss count.
-     * <p>
-     * @return IStats
-     */
-    @Override
-    public IStats getStatistics()
-    {
-        IStats stats = super.getStatistics();
-        stats.setTypeName( "LHMLRU Memory Cache" );
-
-        return stats;
-    }
-
-    // ---------------------------------------------------------- debug methods
-
-    /**
-     * Dump the cache entries from first to last for debugging.
-     */
-    public void dumpCacheEntries()
-    {
-        dumpMap();
-    }
-
-    /**
-     * This can't be implemented.
-     * <p>
-     * @param numberToFree
-     * @return 0
-     * @throws IOException
-     */
-    @Override
-    public int freeElements( int numberToFree )
-        throws IOException
-    {
-        // can't be implemented using the LHM
-        return 0;
-    }
-
-    // ---------------------------------------------------------- extended map
-
-    /**
-     * Implementation of removeEldestEntry in LinkedHashMap
-     */
-    protected class LHMSpooler
-        extends java.util.LinkedHashMap<K, MemoryElementDescriptor<K, V>>
-    {
-        /** Don't change. */
-        private static final long serialVersionUID = -1255907868906762484L;
-
-        /**
-         * Initialize to a small size--for now, 1/2 of max 3rd variable "true" indicates that it
-         * should be access and not time governed. This could be configurable.
-         */
-        public LHMSpooler()
-        {
-            super( (int) ( getCacheAttributes().getMaxObjects() * .5 ), .75F, true );
-        }
-
-        /**
-         * Remove eldest. Automatically called by LinkedHashMap.
-         * <p>
-         * @param eldest
-         * @return true if removed
-         */
-        @SuppressWarnings("synthetic-access")
-        @Override
-        protected boolean removeEldestEntry( Map.Entry<K, MemoryElementDescriptor<K, V>> eldest )
-        {
-            ICacheElement<K, V> element = eldest.getValue().getCacheElement();
-
-            if ( size() <= getCacheAttributes().getMaxObjects() )
-            {
-                return false;
-            }
-            else
-            {
-                log.debug( "LHMLRU max size: {0}. Spooling element, key: {1}",
-                        () -> getCacheAttributes().getMaxObjects(), () -> element.getKey() );
-
-                waterfal( element );
-
-                log.debug( "LHMLRU size: {0}", () -> map.size() );
-            }
-            return true;
-        }
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/lru/LRUMemoryCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/lru/LRUMemoryCache.java
deleted file mode 100644
index a9eb4de..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/lru/LRUMemoryCache.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package org.apache.commons.jcs.engine.memory.lru;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.memory.AbstractDoubleLinkedListMemoryCache;
-import org.apache.commons.jcs.engine.memory.util.MemoryElementDescriptor;
-
-/**
- * A fast reference management system. The least recently used items move to the end of the list and
- * get spooled to disk if the cache hub is configured to use a disk cache. Most of the cache
- * bottlenecks are in IO. There are no io bottlenecks here, it's all about processing power.
- * <p>
- * Even though there are only a few adjustments necessary to maintain the double linked list, we
- * might want to find a more efficient memory manager for large cache regions.
- * <p>
- * The LRUMemoryCache is most efficient when the first element is selected. The smaller the region,
- * the better the chance that this will be the case. &lt; .04 ms per put, p3 866, 1/10 of that per get
- */
-public class LRUMemoryCache<K, V>
-    extends AbstractDoubleLinkedListMemoryCache<K, V>
-{
-    /**
-     * Puts an item to the cache. Removes any pre-existing entries of the same key from the linked
-     * list and adds this one first.
-     * <p>
-     * @param ce The cache element, or entry wrapper
-     * @return MemoryElementDescriptor the new node
-     * @throws IOException
-     */
-    @Override
-    protected MemoryElementDescriptor<K, V> adjustListForUpdate( ICacheElement<K, V> ce )
-        throws IOException
-    {
-        return addFirst( ce );
-    }
-
-    /**
-     * Makes the item the first in the list.
-     * <p>
-     * @param me
-     */
-    @Override
-    protected void adjustListForGet( MemoryElementDescriptor<K, V> me )
-    {
-        list.makeFirst( me );
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/mru/MRUMemoryCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/mru/MRUMemoryCache.java
deleted file mode 100644
index 3638fd9..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/mru/MRUMemoryCache.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package org.apache.commons.jcs.engine.memory.mru;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.memory.AbstractDoubleLinkedListMemoryCache;
-import org.apache.commons.jcs.engine.memory.util.MemoryElementDescriptor;
-
-/**
- * The most recently used items move to the front of the list and get spooled to disk if the cache
- * hub is configured to use a disk cache.
- */
-public class MRUMemoryCache<K, V>
-    extends AbstractDoubleLinkedListMemoryCache<K, V>
-{
-    /**
-     * Adds the item to the front of the list. A put doesn't count as a usage.
-     * <p>
-     * It's not clear if the put operation should be different. Perhaps this should remove the oldest
-     * if full, and then put.
-     * <p>
-     * @param ce
-     * @return MemoryElementDescriptor the new node
-     * @throws IOException
-     */
-    @Override
-    protected MemoryElementDescriptor<K, V> adjustListForUpdate( ICacheElement<K, V> ce )
-        throws IOException
-    {
-        return addFirst( ce );
-    }
-
-    /**
-     * Makes the item the last in the list.
-     * <p>
-     * @param me
-     */
-    @Override
-    protected void adjustListForGet( MemoryElementDescriptor<K, V> me )
-    {
-        list.makeLast( me );
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/shrinking/ShrinkerThread.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/shrinking/ShrinkerThread.java
deleted file mode 100644
index 0cbe492..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/shrinking/ShrinkerThread.java
+++ /dev/null
@@ -1,202 +0,0 @@
-package org.apache.commons.jcs.engine.memory.shrinking;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.Set;
-
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-import org.apache.commons.jcs.engine.control.CompositeCache;
-import org.apache.commons.jcs.engine.control.event.behavior.ElementEventType;
-import org.apache.commons.jcs.engine.memory.behavior.IMemoryCache;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * A background memory shrinker. Memory problems and concurrent modification exception caused by
- * acting directly on an iterator of the underlying memory cache should have been solved.
- * @version $Id$
- */
-public class ShrinkerThread<K, V>
-    implements Runnable
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog( ShrinkerThread.class );
-
-    /** The CompositeCache instance which this shrinker is watching */
-    private final CompositeCache<K, V> cache;
-
-    /** Maximum memory idle time for the whole cache */
-    private final long maxMemoryIdleTime;
-
-    /** Maximum number of items to spool per run. Default is -1, or no limit. */
-    private final int maxSpoolPerRun;
-
-    /** Should we limit the number spooled per run. If so, the maxSpoolPerRun will be used. */
-    private boolean spoolLimit = false;
-
-    /**
-     * Constructor for the ShrinkerThread object.
-     * <p>
-     * @param cache The MemoryCache which the new shrinker should watch.
-     */
-    public ShrinkerThread( CompositeCache<K, V> cache )
-    {
-        super();
-
-        this.cache = cache;
-
-        long maxMemoryIdleTimeSeconds = cache.getCacheAttributes().getMaxMemoryIdleTimeSeconds();
-
-        if ( maxMemoryIdleTimeSeconds < 0 )
-        {
-            this.maxMemoryIdleTime = -1;
-        }
-        else
-        {
-            this.maxMemoryIdleTime = maxMemoryIdleTimeSeconds * 1000;
-        }
-
-        this.maxSpoolPerRun = cache.getCacheAttributes().getMaxSpoolPerRun();
-        if ( this.maxSpoolPerRun != -1 )
-        {
-            this.spoolLimit = true;
-        }
-
-    }
-
-    /**
-     * Main processing method for the ShrinkerThread object
-     */
-    @Override
-    public void run()
-    {
-        shrink();
-    }
-
-    /**
-     * This method is called when the thread wakes up. First the method obtains an array of keys for
-     * the cache region. It iterates through the keys and tries to get the item from the cache
-     * without affecting the last access or position of the item. The item is checked for
-     * expiration, the expiration check has 3 parts:
-     * <ol>
-     * <li>Has the cacheattributes.MaxMemoryIdleTimeSeconds defined for the region been exceeded? If
-     * so, the item should be move to disk.</li> <li>Has the item exceeded MaxLifeSeconds defined in
-     * the element attributes? If so, remove it.</li> <li>Has the item exceeded IdleTime defined in
-     * the element attributes? If so, remove it. If there are event listeners registered for the
-     * cache element, they will be called.</li>
-     * </ol>
-     * TODO Change element event handling to use the queue, then move the queue to the region and
-     *       access via the Cache.
-     */
-    protected void shrink()
-    {
-        log.debug( "Shrinking memory cache for: {0}", () -> this.cache.getCacheName() );
-
-        IMemoryCache<K, V> memCache = cache.getMemoryCache();
-
-        try
-        {
-            Set<K> keys = memCache.getKeySet();
-            int size = keys.size();
-            log.debug( "Keys size: {0}", size );
-
-            int spoolCount = 0;
-
-            for (K key : keys)
-            {
-                final ICacheElement<K, V> cacheElement = memCache.getQuiet( key );
-
-                if ( cacheElement == null )
-                {
-                    continue;
-                }
-
-                IElementAttributes attributes = cacheElement.getElementAttributes();
-
-                boolean remove = false;
-
-                long now = System.currentTimeMillis();
-
-                // If the element is not eternal, check if it should be
-                // removed and remove it if so.
-                if ( !attributes.getIsEternal() )
-                {
-                    remove = cache.isExpired( cacheElement, now,
-                            ElementEventType.EXCEEDED_MAXLIFE_BACKGROUND,
-                            ElementEventType.EXCEEDED_IDLETIME_BACKGROUND );
-
-                    if ( remove )
-                    {
-                        memCache.remove( key );
-                    }
-                }
-
-                // If the item is not removed, check is it has been idle
-                // long enough to be spooled.
-
-                if ( !remove && maxMemoryIdleTime != -1 )
-                {
-                    if ( !spoolLimit || spoolCount < this.maxSpoolPerRun )
-                    {
-                        final long lastAccessTime = attributes.getLastAccessTime();
-
-                        if ( lastAccessTime + maxMemoryIdleTime < now )
-                        {
-                            log.debug( "Exceeded memory idle time: {0}", key );
-
-                            // Shouldn't we ensure that the element is
-                            // spooled before removing it from memory?
-                            // No the disk caches have a purgatory. If it fails
-                            // to spool that does not affect the
-                            // responsibilities of the memory cache.
-
-                            spoolCount++;
-
-                            memCache.remove( key );
-                            memCache.waterfal( cacheElement );
-                        }
-                    }
-                    else
-                    {
-                        log.debug( "spoolCount = \"{0}\"; maxSpoolPerRun = \"{1}\"",
-                                spoolCount, maxSpoolPerRun );
-
-                        // stop processing if limit has been reached.
-                        if ( spoolLimit && spoolCount >= this.maxSpoolPerRun )
-                        {
-                            return;
-                        }
-                    }
-                }
-            }
-        }
-        catch ( Throwable t )
-        {
-            log.info( "Unexpected trouble in shrink cycle", t );
-
-            // concurrent modifications should no longer be a problem
-            // It is up to the IMemoryCache to return an array of keys
-
-            // stop for now
-            return;
-        }
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/soft/SoftReferenceMemoryCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/soft/SoftReferenceMemoryCache.java
deleted file mode 100644
index 6dbb0b3..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/soft/SoftReferenceMemoryCache.java
+++ /dev/null
@@ -1,240 +0,0 @@
-package org.apache.commons.jcs.engine.memory.soft;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.lang.ref.SoftReference;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.LinkedBlockingQueue;
-
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheAttributes;
-import org.apache.commons.jcs.engine.control.CompositeCache;
-import org.apache.commons.jcs.engine.memory.AbstractMemoryCache;
-import org.apache.commons.jcs.engine.memory.util.MemoryElementDescriptor;
-import org.apache.commons.jcs.engine.memory.util.SoftReferenceElementDescriptor;
-import org.apache.commons.jcs.engine.stats.StatElement;
-import org.apache.commons.jcs.engine.stats.behavior.IStatElement;
-import org.apache.commons.jcs.engine.stats.behavior.IStats;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * A JCS IMemoryCache that has {@link SoftReference} to all its values.
- * This cache does not respect {@link ICompositeCacheAttributes#getMaxObjects()}
- * as overflowing is handled by Java GC.
- * <p>
- * The cache also has strong references to a maximum number of objects given by
- * the maxObjects parameter
- *
- * @author halset
- */
-public class SoftReferenceMemoryCache<K, V> extends AbstractMemoryCache<K, V>
-{
-    /** The logger. */
-    private static final Log log = LogManager.getLog(SoftReferenceMemoryCache.class);
-
-    /**
-     * Strong references to the maxObjects number of newest objects.
-     * <p>
-     * Trimming is done by {@link #trimStrongReferences()} instead of by
-     * overriding removeEldestEntry to be able to control waterfalling as easy
-     * as possible
-     */
-    private LinkedBlockingQueue<ICacheElement<K, V>> strongReferences;
-
-    /**
-     * For post reflection creation initialization
-     * <p>
-     * @param hub
-     */
-    @Override
-    public synchronized void initialize( CompositeCache<K, V> hub )
-    {
-        super.initialize( hub );
-        strongReferences = new LinkedBlockingQueue<>();
-        log.info( "initialized Soft Reference Memory Cache for {0}",
-                () -> getCacheName() );
-    }
-
-    /**
-     * @see org.apache.commons.jcs.engine.memory.AbstractMemoryCache#createMap()
-     */
-    @Override
-    public ConcurrentMap<K, MemoryElementDescriptor<K, V>> createMap()
-    {
-        return new ConcurrentHashMap<>();
-    }
-
-    /**
-     * @see org.apache.commons.jcs.engine.memory.behavior.IMemoryCache#getKeySet()
-     */
-    @Override
-    public Set<K> getKeySet()
-    {
-        Set<K> keys = new HashSet<>();
-        for (Map.Entry<K, MemoryElementDescriptor<K, V>> e : map.entrySet())
-        {
-            SoftReferenceElementDescriptor<K, V> sred = (SoftReferenceElementDescriptor<K, V>) e.getValue();
-            if (sred.getCacheElement() != null)
-            {
-                keys.add(e.getKey());
-            }
-        }
-
-        return keys;
-    }
-
-    /**
-     * Returns the current cache size.
-     * <p>
-     * @return The size value
-     */
-    @Override
-    public int getSize()
-    {
-        int size = 0;
-        for (MemoryElementDescriptor<K, V> me : map.values())
-        {
-            SoftReferenceElementDescriptor<K, V> sred = (SoftReferenceElementDescriptor<K, V>) me;
-            if (sred.getCacheElement() != null)
-            {
-                size++;
-            }
-        }
-        return size;
-    }
-
-    /**
-     * @return statistics about the cache
-     */
-    @Override
-    public IStats getStatistics()
-    {
-        IStats stats = super.getStatistics();
-        stats.setTypeName("Soft Reference Memory Cache");
-
-        List<IStatElement<?>> elems = stats.getStatElements();
-        int emptyrefs = map.size() - getSize();
-        elems.add(new StatElement<>("Empty References", Integer.valueOf(emptyrefs)));
-        elems.add(new StatElement<>("Strong References", Integer.valueOf(strongReferences.size())));
-
-        return stats;
-    }
-
-    /**
-     * Update control structures after get
-     * (guarded by the lock)
-     *
-     * @param me the memory element descriptor
-     */
-    @Override
-    protected void lockedGetElement(MemoryElementDescriptor<K, V> me)
-    {
-        ICacheElement<K, V> val = me.getCacheElement();
-        val.getElementAttributes().setLastAccessTimeNow();
-
-        // update the ordering of the strong references
-        strongReferences.add(val);
-        trimStrongReferences();
-    }
-
-    /**
-     * Remove element from control structure
-     * (guarded by the lock)
-     *
-     * @param me the memory element descriptor
-     */
-    @Override
-    protected void lockedRemoveElement(MemoryElementDescriptor<K, V> me)
-    {
-        strongReferences.remove(me.getCacheElement());
-    }
-
-    /**
-     * Removes all cached items from the cache control structures.
-     * (guarded by the lock)
-     */
-    @Override
-    protected void lockedRemoveAll()
-    {
-        strongReferences.clear();
-    }
-
-    /**
-     * Puts an item to the cache.
-     * <p>
-     * @param ce Description of the Parameter
-     * @throws IOException Description of the Exception
-     */
-    @Override
-    public void update(ICacheElement<K, V> ce) throws IOException
-    {
-        putCnt.incrementAndGet();
-        ce.getElementAttributes().setLastAccessTimeNow();
-
-        lock.lock();
-
-        try
-        {
-            map.put(ce.getKey(), new SoftReferenceElementDescriptor<>(ce));
-            strongReferences.add(ce);
-            trimStrongReferences();
-        }
-        finally
-        {
-            lock.unlock();
-        }
-    }
-
-    /**
-     * Trim the number of strong references to equal or below the number given
-     * by the maxObjects parameter.
-     */
-    private void trimStrongReferences()
-    {
-        int max = getCacheAttributes().getMaxObjects();
-        int startsize = strongReferences.size();
-
-        for (int cursize = startsize; cursize > max; cursize--)
-        {
-            ICacheElement<K, V> ce = strongReferences.poll();
-            waterfal(ce);
-        }
-    }
-
-    /**
-     * This can't be implemented.
-     * <p>
-     * @param numberToFree
-     * @return 0
-     * @throws IOException
-     */
-    @Override
-    public int freeElements(int numberToFree) throws IOException
-    {
-        return 0;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/util/MemoryElementDescriptor.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/util/MemoryElementDescriptor.java
deleted file mode 100644
index 613f2b5..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/util/MemoryElementDescriptor.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package org.apache.commons.jcs.engine.memory.util;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.utils.struct.DoubleLinkedListNode;
-
-/**
- * This wrapper is needed for double linked lists.
- */
-public class MemoryElementDescriptor<K, V>
-    extends DoubleLinkedListNode<ICacheElement<K, V>>
-{
-    /** Don't change */
-    private static final long serialVersionUID = -1905161209035522460L;
-
-    /**
-     * Constructs a usable MemoryElementDescriptor.
-     * <p>
-     * @param ce
-     */
-    public MemoryElementDescriptor( ICacheElement<K, V> ce )
-    {
-        super( ce );
-    }
-
-    /**
-     * Get the cache element
-     *
-     * @return the ce
-     */
-    public ICacheElement<K, V> getCacheElement()
-    {
-        return getPayload();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/util/SoftReferenceElementDescriptor.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/util/SoftReferenceElementDescriptor.java
deleted file mode 100644
index 89edf02..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/util/SoftReferenceElementDescriptor.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package org.apache.commons.jcs.engine.memory.util;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.lang.ref.SoftReference;
-
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-
-/**
- * This wrapper is needed for double linked lists.
- */
-public class SoftReferenceElementDescriptor<K, V>
-    extends MemoryElementDescriptor<K, V>
-{
-    /** Don't change */
-    private static final long serialVersionUID = -1905161209035522460L;
-
-    /** The CacheElement wrapped by this descriptor */
-    private final SoftReference<ICacheElement<K, V>> srce;
-
-    /**
-     * Constructs a usable MemoryElementDescriptor.
-     * <p>
-     * @param ce
-     */
-    public SoftReferenceElementDescriptor( ICacheElement<K, V> ce )
-    {
-        super( null );
-        this.srce = new SoftReference<>(ce);
-    }
-
-    /**
-     * @return the ce
-     */
-    @Override
-    public ICacheElement<K, V> getCacheElement()
-    {
-        if (srce != null)
-        {
-            return srce.get();
-        }
-
-        return null;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/stats/CacheStats.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/stats/CacheStats.java
deleted file mode 100644
index eacede7..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/stats/CacheStats.java
+++ /dev/null
@@ -1,116 +0,0 @@
-package org.apache.commons.jcs.engine.stats;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.stats.behavior.ICacheStats;
-import org.apache.commons.jcs.engine.stats.behavior.IStats;
-
-import java.util.List;
-
-/**
- * This class stores cache historical and statistics data for a region.
- * <p>
- * Only the composite cache knows what the hit count across all auxiliaries is.
- */
-public class CacheStats
-    extends Stats
-    implements ICacheStats
-{
-    /** Don't change. */
-    private static final long serialVersionUID = 529914708798168590L;
-
-    /** The region */
-    private String regionName = null;
-
-    /** What that auxiliaries are reporting. */
-    private List<IStats> auxStats = null;
-
-    /**
-     * Stats are for a region, though auxiliary data may be for more.
-     * <p>
-     * @return The region name
-     */
-    @Override
-    public String getRegionName()
-    {
-        return regionName;
-    }
-
-    /**
-     * Stats are for a region, though auxiliary data may be for more.
-     * <p>
-     * @param name - The region name
-     */
-    @Override
-    public void setRegionName( String name )
-    {
-        regionName = name;
-    }
-
-    /**
-     * @return IStats[]
-     */
-    @Override
-    public List<IStats> getAuxiliaryCacheStats()
-    {
-        return auxStats;
-    }
-
-    /**
-     * @param stats
-     */
-    @Override
-    public void setAuxiliaryCacheStats( List<IStats> stats )
-    {
-        auxStats = stats;
-    }
-
-    /**
-     * @return readable string that can be logged.
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder buf = new StringBuilder();
-
-        buf.append( "Region Name = " + regionName );
-
-        if ( getStatElements() != null )
-        {
-            for ( Object stat : getStatElements() )
-            {
-                buf.append( "\n" );
-                buf.append( stat );
-            }
-        }
-
-        if ( auxStats != null )
-        {
-            for ( Object auxStat : auxStats )
-            {
-                buf.append( "\n" );
-                buf.append( "---------------------------" );
-                buf.append( auxStat );
-            }
-        }
-
-        return buf.toString();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/stats/StatElement.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/stats/StatElement.java
deleted file mode 100644
index fd2b345..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/stats/StatElement.java
+++ /dev/null
@@ -1,104 +0,0 @@
-package org.apache.commons.jcs.engine.stats;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.stats.behavior.IStatElement;
-
-/**
- * This is a stat data holder.
- */
-public class StatElement<V>
-    implements IStatElement<V>
-{
-    /** Don't change */
-    private static final long serialVersionUID = -2982373725267618092L;
-
-    /** name of the stat */
-    private String name = null;
-
-    /** the data */
-    private V data = null;
-
-    /**
-     * Constructor
-     *
-     * @param name
-     * @param data
-     */
-    public StatElement(String name, V data)
-    {
-        super();
-        this.name = name;
-        this.data = data;
-    }
-
-    /**
-     * Get the name of the stat element, ex. HitCount
-     * <p>
-     * @return the stat element name
-     */
-    @Override
-    public String getName()
-    {
-        return name;
-    }
-
-    /**
-     * @param name
-     */
-    @Override
-    public void setName( String name )
-    {
-        this.name = name;
-    }
-
-    /**
-     * Get the data, ex. for hit count you would get a value for some number.
-     * <p>
-     * @return data
-     */
-    @Override
-    public V getData()
-    {
-        return data;
-    }
-
-    /**
-     * Set the data for this element.
-     * <p>
-     * @param data
-     */
-    @Override
-    public void setData( V data )
-    {
-        this.data = data;
-    }
-
-    /**
-     * @return a readable string.
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder buf = new StringBuilder();
-        buf.append( name ).append(" = ").append( data );
-        return buf.toString();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/stats/Stats.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/stats/Stats.java
deleted file mode 100644
index dfbf388..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/stats/Stats.java
+++ /dev/null
@@ -1,99 +0,0 @@
-package org.apache.commons.jcs.engine.stats;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.stats.behavior.IStatElement;
-import org.apache.commons.jcs.engine.stats.behavior.IStats;
-
-import java.util.List;
-
-/**
- * @author aaronsm
- */
-public class Stats
-    implements IStats
-{
-    /** Don't change */
-    private static final long serialVersionUID = 227327902875154010L;
-
-    /** The stats */
-    private List<IStatElement<?>> stats = null;
-
-    /** The type of stat */
-    private String typeName = null;
-
-    /**
-     * @return IStatElement[]
-     */
-    @Override
-    public List<IStatElement<?>> getStatElements()
-    {
-        return stats;
-    }
-
-    /**
-     * @param stats
-     */
-    @Override
-    public void setStatElements( List<IStatElement<?>> stats )
-    {
-        this.stats = stats;
-    }
-
-    /**
-     * @return typeName
-     */
-    @Override
-    public String getTypeName()
-    {
-        return typeName;
-    }
-
-    /**
-     * @param name
-     */
-    @Override
-    public void setTypeName( String name )
-    {
-        typeName = name;
-    }
-
-    /**
-     * @return the stats in a readable string
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder buf = new StringBuilder();
-
-        buf.append( typeName );
-
-        if ( stats != null )
-        {
-            for (Object stat : stats)
-            {
-                buf.append( "\n" );
-                buf.append( stat );
-            }
-        }
-
-        return buf.toString();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/stats/behavior/ICacheStats.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/stats/behavior/ICacheStats.java
deleted file mode 100644
index 4efd282..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/stats/behavior/ICacheStats.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package org.apache.commons.jcs.engine.stats.behavior;
-
-import java.util.List;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * This holds stat information on a region. It contains both auxiliary and core stats.
- */
-public interface ICacheStats
-    extends IStats
-{
-    /**
-     * Stats are for a region, though auxiliary data may be for more.
-     * <p>
-     * @return The region name
-     */
-    String getRegionName();
-
-    /**
-     * @param name
-     */
-    void setRegionName( String name );
-
-    /**
-     * @return IStats[]
-     */
-    List<IStats> getAuxiliaryCacheStats();
-
-    /**
-     * @param stats
-     */
-    void setAuxiliaryCacheStats( List<IStats> stats );
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/stats/behavior/IStatElement.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/stats/behavior/IStatElement.java
deleted file mode 100644
index 64139df..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/stats/behavior/IStatElement.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package org.apache.commons.jcs.engine.stats.behavior;
-
-import java.io.Serializable;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * IAuxiliaryCacheStats will hold these IStatElements.
- */
-public interface IStatElement<V> extends Serializable
-{
-    /**
-     * Get the name of the stat element, ex. HitCount
-     * <p>
-     * @return the stat element name
-     */
-    String getName();
-
-    /**
-     * @param name
-     */
-    void setName( String name );
-
-    /**
-     * Get the data, ex. for hit count you would get a value for some number.
-     * <p>
-     * @return data
-     */
-    V getData();
-
-    /**
-     * Set the data for this element.
-     * <p>
-     * @param data
-     */
-    void setData( V data );
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/stats/behavior/IStats.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/stats/behavior/IStats.java
deleted file mode 100644
index 352225a..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/stats/behavior/IStats.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package org.apache.commons.jcs.engine.stats.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.Serializable;
-import java.util.List;
-
-/**
- * This interface defines the common behavior for a stats holder.
- *
- * @author aaronsm
- *
- */
-public interface IStats
-    extends Serializable
-{
-
-    /**
-     * Return generic statistical or historical data.
-     *
-     * @return list of IStatElements
-     */
-    List<IStatElement<?>> getStatElements();
-
-    /**
-     * Set the generic statistical or historical data.
-     *
-     * @param stats
-     */
-    void setStatElements( List<IStatElement<?>> stats );
-
-    /**
-     * Get the type name, such as "LRU Memory Cache." No formal type is defined.
-     *
-     * @return String
-     */
-    String getTypeName();
-
-    /**
-     * Set the type name, such as "LRU Memory Cache." No formal type is defined.
-     * If we need formal types, we can use the cachetype param
-     *
-     * @param name
-     */
-    void setTypeName( String name );
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/io/ObjectInputStreamClassLoaderAware.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/io/ObjectInputStreamClassLoaderAware.java
deleted file mode 100644
index 09c8f55..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/io/ObjectInputStreamClassLoaderAware.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.io;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.ObjectInputStream;
-import java.io.ObjectStreamClass;
-import java.lang.reflect.Proxy;
-
-public class ObjectInputStreamClassLoaderAware extends ObjectInputStream {
-    private final ClassLoader classLoader;
-
-    public ObjectInputStreamClassLoaderAware(final InputStream in, final ClassLoader classLoader) throws IOException {
-        super(in);
-        this.classLoader = classLoader != null ? classLoader : Thread.currentThread().getContextClassLoader();
-    }
-
-    @Override
-    protected Class<?> resolveClass(final ObjectStreamClass desc) throws ClassNotFoundException {
-        return Class.forName(BlacklistClassResolver.DEFAULT.check(desc.getName()), false, classLoader);
-    }
-
-    @Override
-    protected Class<?> resolveProxyClass(final String[] interfaces) throws IOException, ClassNotFoundException {
-        final Class<?>[] cinterfaces = new Class[interfaces.length];
-        for (int i = 0; i < interfaces.length; i++) {
-            cinterfaces[i] = Class.forName(interfaces[i], false, classLoader);
-        }
-
-        try {
-            return Proxy.getProxyClass(classLoader, cinterfaces);
-        } catch (IllegalArgumentException e) {
-            throw new ClassNotFoundException(null, e);
-        }
-    }
-
-    private static class BlacklistClassResolver {
-        private static final BlacklistClassResolver DEFAULT = new BlacklistClassResolver(
-            toArray(System.getProperty(
-                "jcs.serialization.class.blacklist",
-                "org.codehaus.groovy.runtime.,org.apache.commons.collections.functors.,org.apache.xalan")),
-            toArray(System.getProperty("jcs.serialization.class.whitelist")));
-
-        private final String[] blacklist;
-        private final String[] whitelist;
-
-        protected BlacklistClassResolver(final String[] blacklist, final String[] whitelist) {
-            this.whitelist = whitelist;
-            this.blacklist = blacklist;
-        }
-
-        protected boolean isBlacklisted(final String name) {
-            return (whitelist != null && !contains(whitelist, name)) || contains(blacklist, name);
-        }
-
-        public final String check(final String name) {
-            if (isBlacklisted(name)) {
-                throw new SecurityException(name + " is not whitelisted as deserialisable, prevented before loading.");
-            }
-            return name;
-        }
-
-        private static String[] toArray(final String property) {
-            return property == null ? null : property.split(" *, *");
-        }
-
-        private static boolean contains(final String[] list, String name) {
-            if (list != null) {
-                for (final String white : list) {
-                    if (name.startsWith(white)) {
-                        return true;
-                    }
-                }
-            }
-            return false;
-        }
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/log/JulLogAdapter.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/log/JulLogAdapter.java
deleted file mode 100644
index fba069f..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/log/JulLogAdapter.java
+++ /dev/null
@@ -1,574 +0,0 @@
-package org.apache.commons.jcs.log;
-
-import java.util.function.Supplier;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-/**
- * This is a wrapper around the <code>java.util.logging.Logger</code> implementing our own
- * <code>Log</code> interface.
- * <p>
- * This is the mapping of the log levels
- * </p>
- * <pre>
- * Java Level   Log Level
- * SEVERE       FATAL
- * SEVERE       ERROR
- * WARNING      WARN
- * INFO         INFO
- * FINE         DEBUG
- * FINER        TRACE
- * </pre>
- */
-public class JulLogAdapter implements Log
-{
-    private Logger logger;
-
-    /**
-     * Construct a JUL Logger wrapper
-     *
-     * @param logger the JUL Logger
-     */
-    public JulLogAdapter(Logger logger)
-    {
-        super();
-        this.logger = logger;
-    }
-
-    private void log(Level level, String message)
-    {
-        if (logger.isLoggable(level))
-        {
-            logger.logp(level, logger.getName(), "", message);
-        }
-    }
-
-    private void log(Level level, Object message)
-    {
-        if (logger.isLoggable(level))
-        {
-            if (message instanceof Throwable)
-            {
-                logger.logp(level, logger.getName(), "", "Exception:", (Throwable) message);
-            }
-            else
-            {
-                logger.logp(level, logger.getName(), "",
-                        message == null ? null : message.toString());
-            }
-        }
-    }
-
-    private void log(Level level, String message, Throwable t)
-    {
-        if (logger.isLoggable(level))
-        {
-            logger.logp(level, logger.getName(), "", message, t);
-        }
-    }
-
-    private void log(Level level, String message, Object... params)
-    {
-        if (logger.isLoggable(level))
-        {
-            MessageFormatter formatter = new MessageFormatter(message, params);
-            if (formatter.hasThrowable())
-            {
-                logger.logp(level, logger.getName(), "",
-                        formatter.getFormattedMessage(), formatter.getThrowable());
-            }
-            else
-            {
-                logger.logp(level, logger.getName(), "",
-                        formatter.getFormattedMessage());
-            }
-        }
-    }
-
-    private void log(Level level, String message, Supplier<?>... paramSuppliers)
-    {
-        if (logger.isLoggable(level))
-        {
-            MessageFormatter formatter = new MessageFormatter(message, paramSuppliers);
-            if (formatter.hasThrowable())
-            {
-                logger.logp(level, logger.getName(), "",
-                        formatter.getFormattedMessage(), formatter.getThrowable());
-            }
-            else
-            {
-                logger.logp(level, logger.getName(), "",
-                        formatter.getFormattedMessage());
-            }
-        }
-    }
-
-    /**
-     * Logs a message object with the DEBUG level.
-     *
-     * @param message the message string to log.
-     */
-    @Override
-    public void debug(String message)
-    {
-        log(Level.FINE, message);
-    }
-
-    /**
-     * Logs a message object with the DEBUG level.
-     *
-     * @param message the message object to log.
-     */
-    @Override
-    public void debug(Object message)
-    {
-        log(Level.FINE, message);
-    }
-
-    /**
-     * Logs a message with parameters at the DEBUG level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param params parameters to the message.
-     * @see #getMessageFactory()
-     */
-    @Override
-    public void debug(String message, Object... params)
-    {
-        log(Level.FINE, message, params);
-    }
-
-    /**
-     * Logs a message with parameters which are only to be constructed if the
-     * logging level is the DEBUG level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param paramSuppliers An array of functions, which when called,
-     *        produce the desired log message parameters.
-     */
-    @Override
-    public void debug(String message, Supplier<?>... paramSuppliers)
-    {
-        log(Level.FINE, message, paramSuppliers);
-    }
-
-    /**
-     * Logs a message at the DEBUG level including the stack trace of the {@link Throwable}
-     * <code>t</code> passed as parameter.
-     *
-     * @param message the message to log.
-     * @param t the exception to log, including its stack trace.
-     */
-    @Override
-    public void debug(String message, Throwable t)
-    {
-        log(Level.FINE, message, t);
-    }
-
-    /**
-     * Logs a message object with the ERROR level.
-     *
-     * @param message the message string to log.
-     */
-    @Override
-    public void error(String message)
-    {
-        log(Level.SEVERE, message);
-    }
-
-    /**
-     * Logs a message object with the ERROR level.
-     *
-     * @param message the message object to log.
-     */
-    @Override
-    public void error(Object message)
-    {
-        log(Level.SEVERE, message);
-    }
-
-    /**
-     * Logs a message with parameters at the ERROR level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param params parameters to the message.
-     */
-    @Override
-    public void error(String message, Object... params)
-    {
-        log(Level.SEVERE, message, params);
-    }
-
-    /**
-     * Logs a message with parameters which are only to be constructed if the
-     * logging level is the ERROR level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param paramSuppliers An array of functions, which when called, produce
-     *        the desired log message parameters.
-     * @since 2.4
-     */
-    @Override
-    public void error(String message, Supplier<?>... paramSuppliers)
-    {
-        log(Level.SEVERE, message, paramSuppliers);
-    }
-
-    /**
-     * Logs a message at the ERROR level including the stack trace of the {@link Throwable}
-     * <code>t</code> passed as parameter.
-     *
-     * @param message the message object to log.
-     * @param t the exception to log, including its stack trace.
-     */
-    @Override
-    public void error(String message, Throwable t)
-    {
-        log(Level.SEVERE, message, t);
-    }
-
-    /**
-     * Logs a message object with the FATAL level.
-     *
-     * @param message the message string to log.
-     */
-    @Override
-    public void fatal(String message)
-    {
-        log(Level.SEVERE, message);
-    }
-
-    /**
-     * Logs a message object with the FATAL level.
-     *
-     * @param message the message object to log.
-     */
-    @Override
-    public void fatal(Object message)
-    {
-        log(Level.SEVERE, message);
-    }
-
-    /**
-     * Logs a message with parameters at the FATAL level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param params parameters to the message.
-     */
-    @Override
-    public void fatal(String message, Object... params)
-    {
-        log(Level.SEVERE, message, params);
-    }
-
-    /**
-     * Logs a message with parameters which are only to be constructed if the
-     * logging level is the FATAL level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param paramSuppliers An array of functions, which when called, produce the
-     *        desired log message parameters.
-     */
-    @Override
-    public void fatal(String message, Supplier<?>... paramSuppliers)
-    {
-        log(Level.SEVERE, message, paramSuppliers);
-    }
-
-    /**
-     * Logs a message at the FATAL level including the stack trace of the {@link Throwable}
-     * <code>t</code> passed as parameter.
-     *
-     * @param message the message object to log.
-     * @param t the exception to log, including its stack trace.
-     */
-    @Override
-    public void fatal(String message, Throwable t)
-    {
-        log(Level.SEVERE, message, t);
-    }
-
-    /**
-     * Gets the logger name.
-     *
-     * @return the logger name.
-     */
-    @Override
-    public String getName()
-    {
-        return logger.getName();
-    }
-
-    /**
-     * Logs a message object with the INFO level.
-     *
-     * @param message the message string to log.
-     */
-    @Override
-    public void info(String message)
-    {
-        log(Level.INFO, message);
-    }
-
-    /**
-     * Logs a message object with the INFO level.
-     *
-     * @param message the message object to log.
-     */
-    @Override
-    public void info(Object message)
-    {
-        log(Level.INFO, message);
-    }
-
-    /**
-     * Logs a message with parameters at the INFO level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param params parameters to the message.
-     */
-    @Override
-    public void info(String message, Object... params)
-    {
-        log(Level.INFO, message, params);
-    }
-
-    /**
-     * Logs a message with parameters which are only to be constructed if the
-     * logging level is the INFO level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param paramSuppliers An array of functions, which when called, produce
-     *        the desired log message parameters.
-     */
-    @Override
-    public void info(String message, Supplier<?>... paramSuppliers)
-    {
-        log(Level.INFO, message, paramSuppliers);
-    }
-
-    /**
-     * Logs a message at the INFO level including the stack trace of the {@link Throwable}
-     * <code>t</code> passed as parameter.
-     *
-     * @param message the message object to log.
-     * @param t the exception to log, including its stack trace.
-     */
-    @Override
-    public void info(String message, Throwable t)
-    {
-        log(Level.INFO, message, t);
-    }
-
-    /**
-     * Checks whether this Logger is enabled for the DEBUG Level.
-     *
-     * @return boolean - {@code true} if this Logger is enabled for level DEBUG, {@code false}
-     *         otherwise.
-     */
-    @Override
-    public boolean isDebugEnabled()
-    {
-        return logger.isLoggable(Level.FINE);
-    }
-
-    /**
-     * Checks whether this Logger is enabled for the ERROR Level.
-     *
-     * @return boolean - {@code true} if this Logger is enabled for level ERROR, {@code false}
-     *         otherwise.
-     */
-    @Override
-    public boolean isErrorEnabled()
-    {
-        return logger.isLoggable(Level.SEVERE);
-    }
-
-    /**
-     * Checks whether this Logger is enabled for the FATAL Level.
-     *
-     * @return boolean - {@code true} if this Logger is enabled for level FATAL, {@code false}
-     *         otherwise.
-     */
-    @Override
-    public boolean isFatalEnabled()
-    {
-        return logger.isLoggable(Level.SEVERE);
-    }
-
-    /**
-     * Checks whether this Logger is enabled for the INFO Level.
-     *
-     * @return boolean - {@code true} if this Logger is enabled for level INFO, {@code false}
-     *         otherwise.
-     */
-    @Override
-    public boolean isInfoEnabled()
-    {
-        return logger.isLoggable(Level.INFO);
-    }
-
-    /**
-     * Checks whether this Logger is enabled for the TRACE level.
-     *
-     * @return boolean - {@code true} if this Logger is enabled for level TRACE, {@code false}
-     *         otherwise.
-     */
-    @Override
-    public boolean isTraceEnabled()
-    {
-        return logger.isLoggable(Level.FINER);
-    }
-
-    /**
-     * Checks whether this Logger is enabled for the WARN Level.
-     *
-     * @return boolean - {@code true} if this Logger is enabled for level WARN, {@code false}
-     *         otherwise.
-     */
-    @Override
-    public boolean isWarnEnabled()
-    {
-        return logger.isLoggable(Level.WARNING);
-    }
-
-    /**
-     * Logs a message object with the TRACE level.
-     *
-     * @param message the message string to log.
-     */
-    @Override
-    public void trace(String message)
-    {
-        log(Level.FINER, message);
-    }
-
-    /**
-     * Logs a message object with the TRACE level.
-     *
-     * @param message the message object to log.
-     */
-    @Override
-    public void trace(Object message)
-    {
-        log(Level.FINER, message);
-    }
-
-    /**
-     * Logs a message with parameters at the TRACE level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param params parameters to the message.
-     */
-    @Override
-    public void trace(String message, Object... params)
-    {
-        log(Level.FINER, message, params);
-    }
-
-    /**
-     * Logs a message with parameters which are only to be constructed if the
-     * logging level is the TRACE level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param paramSuppliers An array of functions, which when called, produce
-     *        the desired log message parameters.
-     */
-    @Override
-    public void trace(String message, Supplier<?>... paramSuppliers)
-    {
-        log(Level.FINER, message, paramSuppliers);
-    }
-
-    /**
-     * Logs a message at the TRACE level including the stack trace of the {@link Throwable}
-     * <code>t</code> passed as parameter.
-     *
-     * @param message the message object to log.
-     * @param t the exception to log, including its stack trace.
-     * @see #debug(String)
-     */
-    @Override
-    public void trace(String message, Throwable t)
-    {
-        log(Level.FINER, message, t);
-    }
-
-    /**
-     * Logs a message object with the WARN level.
-     *
-     * @param message the message string to log.
-     */
-    @Override
-    public void warn(String message)
-    {
-        log(Level.WARNING, message);
-    }
-
-    /**
-     * Logs a message object with the WARN level.
-     *
-     * @param message the message object to log.
-     */
-    @Override
-    public void warn(Object message)
-    {
-        log(Level.WARNING, message);
-    }
-
-    /**
-     * Logs a message with parameters at the WARN level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param params parameters to the message.
-     */
-    @Override
-    public void warn(String message, Object... params)
-    {
-        log(Level.WARNING, message, params);
-    }
-
-    /**
-     * Logs a message with parameters which are only to be constructed if the
-     * logging level is the WARN level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param paramSuppliers An array of functions, which when called, produce
-     *        the desired log message parameters.
-     */
-    @Override
-    public void warn(String message, Supplier<?>... paramSuppliers)
-    {
-        log(Level.WARNING, message, paramSuppliers);
-    }
-
-    /**
-     * Logs a message at the WARN level including the stack trace of the {@link Throwable}
-     * <code>t</code> passed as parameter.
-     *
-     * @param message the message object to log.
-     * @param t the exception to log, including its stack trace.
-     */
-    @Override
-    public void warn(String message, Throwable t)
-    {
-        log(Level.WARNING, message, t);
-    }
-}
\ No newline at end of file
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/log/JulLogFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/log/JulLogFactory.java
deleted file mode 100644
index c1e21eb..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/log/JulLogFactory.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package org.apache.commons.jcs.log;
-
-import java.util.logging.Logger;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-/**
- * This is a SPI factory implementation for java.util.logging
- */
-public class JulLogFactory implements LogFactory
-{
-    /**
-     * Return the name of the Log subsystem managed by this factory
-     *
-     * @return the name of the log subsystem
-     */
-    @Override
-    public String getName()
-    {
-        return "jul";
-    }
-
-    /**
-     * Shutdown the logging system if the logging system supports it.
-     */
-    @Override
-    public void shutdown()
-    {
-        // do nothing
-    }
-
-    /**
-     * Returns a Log using the fully qualified name of the Class as the Log
-     * name.
-     *
-     * @param clazz
-     *            The Class whose name should be used as the Log name.
-     *
-     * @return The Log.
-     */
-    @Override
-    public Log getLog(final Class<?> clazz)
-    {
-        Logger logger = Logger.getLogger(clazz.getName());
-        return new JulLogAdapter(logger);
-    }
-
-    /**
-     * Returns a Log with the specified name.
-     *
-     * @param name
-     *            The logger name.
-     * @return The Log.
-     */
-    @Override
-    public Log getLog(final String name)
-    {
-        Logger logger = Logger.getLogger(name);
-        return new JulLogAdapter(logger);
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/log/Log.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/log/Log.java
deleted file mode 100644
index b548db4..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/log/Log.java
+++ /dev/null
@@ -1,343 +0,0 @@
-package org.apache.commons.jcs.log;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-import java.util.function.Supplier;
-
-/**
- * This is a borrowed and stripped-down version of the log4j2 Logger interface.
- * All logging operations, except configuration, are done through this interface.
- *
- * <p>
- * The canonical way to obtain a Logger for a class is through {@link LogManager#getLog()}.
- * Typically, each class should get its own Log named after its fully qualified class name
- * </p>
- *
- * <pre>
- * public class MyClass {
- *     private static final Log log = LogManager.getLog(MyClass.class);
- *     // ...
- * }
- * </pre>
- */
-public interface Log
-{
-    /**
-     * Logs a message object with the DEBUG level.
-     *
-     * @param message the message string to log.
-     */
-    void debug(String message);
-
-    /**
-     * Logs a message object with the DEBUG level.
-     *
-     * @param message the message object to log.
-     */
-    void debug(Object message);
-
-    /**
-     * Logs a message with parameters at the DEBUG level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param params parameters to the message.
-     */
-    void debug(String message, Object... params);
-
-    /**
-     * Logs a message with parameters which are only to be constructed if the
-     * logging level is the DEBUG level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param paramSuppliers An array of functions, which when called, produce
-     *        the desired log message parameters.
-     */
-    void debug(String message, Supplier<?>... paramSuppliers);
-
-    /**
-     * Logs a message at the DEBUG level including the stack trace of the {@link Throwable}
-     * <code>t</code> passed as parameter.
-     *
-     * @param message the message to log.
-     * @param t the exception to log, including its stack trace.
-     */
-    void debug(String message, Throwable t);
-
-    /**
-     * Logs a message object with the ERROR level.
-     *
-     * @param message the message string to log.
-     */
-    void error(String message);
-
-    /**
-     * Logs a message object with the ERROR level.
-     *
-     * @param message the message object to log.
-     */
-    void error(Object message);
-
-    /**
-     * Logs a message with parameters at the ERROR level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param params parameters to the message.
-     */
-    void error(String message, Object... params);
-
-    /**
-     * Logs a message with parameters which are only to be constructed if the
-     * logging level is the ERROR level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param paramSuppliers An array of functions, which when called, produce
-     *        the desired log message parameters.
-     */
-    void error(String message, Supplier<?>... paramSuppliers);
-
-    /**
-     * Logs a message at the ERROR level including the stack trace of the {@link Throwable}
-     * <code>t</code> passed as parameter.
-     *
-     * @param message the message object to log.
-     * @param t the exception to log, including its stack trace.
-     */
-    void error(String message, Throwable t);
-
-    /**
-     * Logs a message object with the FATAL level.
-     *
-     * @param message the message string to log.
-     */
-    void fatal(String message);
-
-    /**
-     * Logs a message object with the FATAL level.
-     *
-     * @param message the message object to log.
-     */
-    void fatal(Object message);
-
-    /**
-     * Logs a message with parameters at the FATAL level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param params parameters to the message.
-     */
-    void fatal(String message, Object... params);
-
-    /**
-     * Logs a message with parameters which are only to be constructed if the
-     * logging level is the FATAL level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param paramSuppliers An array of functions, which when called, produce
-     *        the desired log message parameters.
-     */
-    void fatal(String message, Supplier<?>... paramSuppliers);
-
-    /**
-     * Logs a message at the FATAL level including the stack trace of the {@link Throwable}
-     * <code>t</code> passed as parameter.
-     *
-     * @param message the message object to log.
-     * @param t the exception to log, including its stack trace.
-     */
-    void fatal(String message, Throwable t);
-
-    /**
-     * Gets the logger name.
-     *
-     * @return the logger name.
-     */
-    String getName();
-
-    /**
-     * Logs a message object with the INFO level.
-     *
-     * @param message the message string to log.
-     */
-    void info(String message);
-
-    /**
-     * Logs a message object with the INFO level.
-     *
-     * @param message the message object to log.
-     */
-    void info(Object message);
-
-    /**
-     * Logs a message with parameters at the INFO level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param params parameters to the message.
-     */
-    void info(String message, Object... params);
-
-    /**
-     * Logs a message with parameters which are only to be constructed if the
-     * logging level is the INFO level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param paramSuppliers An array of functions, which when called, produce
-     *        the desired log message parameters.
-     */
-    void info(String message, Supplier<?>... paramSuppliers);
-
-    /**
-     * Logs a message at the INFO level including the stack trace of the {@link Throwable}
-     * <code>t</code> passed as parameter.
-     *
-     * @param message the message object to log.
-     * @param t the exception to log, including its stack trace.
-     */
-    void info(String message, Throwable t);
-
-    /**
-     * Checks whether this Logger is enabled for the DEBUG Level.
-     *
-     * @return boolean - {@code true} if this Logger is enabled for level DEBUG, {@code false}
-     *         otherwise.
-     */
-    boolean isDebugEnabled();
-
-    /**
-     * Checks whether this Logger is enabled for the ERROR Level.
-     *
-     * @return boolean - {@code true} if this Logger is enabled for level ERROR, {@code false}
-     *         otherwise.
-     */
-    boolean isErrorEnabled();
-
-    /**
-     * Checks whether this Logger is enabled for the FATAL Level.
-     *
-     * @return boolean - {@code true} if this Logger is enabled for level FATAL, {@code false}
-     *         otherwise.
-     */
-    boolean isFatalEnabled();
-
-    /**
-     * Checks whether this Logger is enabled for the INFO Level.
-     *
-     * @return boolean - {@code true} if this Logger is enabled for level INFO, {@code false}
-     *         otherwise.
-     */
-    boolean isInfoEnabled();
-
-    /**
-     * Checks whether this Logger is enabled for the TRACE level.
-     *
-     * @return boolean - {@code true} if this Logger is enabled for level TRACE, {@code false}
-     *         otherwise.
-     */
-    boolean isTraceEnabled();
-
-    /**
-     * Checks whether this Logger is enabled for the WARN Level.
-     *
-     * @return boolean - {@code true} if this Logger is enabled for level WARN, {@code false}
-     *         otherwise.
-     */
-    boolean isWarnEnabled();
-
-    /**
-     * Logs a message object with the TRACE level.
-     *
-     * @param message the message string to log.
-     */
-    void trace(String message);
-
-    /**
-     * Logs a message object with the TRACE level.
-     *
-     * @param message the message object to log.
-     */
-    void trace(Object message);
-
-    /**
-     * Logs a message with parameters at the TRACE level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param params parameters to the message.
-     * @see #getMessageFactory()
-     */
-    void trace(String message, Object... params);
-
-    /**
-     * Logs a message with parameters which are only to be constructed if the
-     * logging level is the TRACE level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param paramSuppliers An array of functions, which when called, produce
-     *        the desired log message parameters.
-     */
-    void trace(String message, Supplier<?>... paramSuppliers);
-
-    /**
-     * Logs a message at the TRACE level including the stack trace of the {@link Throwable}
-     * <code>t</code> passed as parameter.
-     *
-     * @param message the message object to log.
-     * @param t the exception to log, including its stack trace.
-     * @see #debug(String)
-     */
-    void trace(String message, Throwable t);
-
-    /**
-     * Logs a message object with the WARN level.
-     *
-     * @param message the message string to log.
-     */
-    void warn(String message);
-
-    /**
-     * Logs a message object with the WARN level.
-     *
-     * @param message the message object to log.
-     */
-    void warn(Object message);
-
-    /**
-     * Logs a message with parameters at the WARN level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param params parameters to the message.
-     * @see #getMessageFactory()
-     */
-    void warn(String message, Object... params);
-
-    /**
-     * Logs a message with parameters which are only to be constructed if the
-     * logging level is the WARN level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param paramSuppliers An array of functions, which when called, produce
-     *        the desired log message parameters.
-     */
-    void warn(String message, Supplier<?>... paramSuppliers);
-
-    /**
-     * Logs a message at the WARN level including the stack trace of the {@link Throwable}
-     * <code>t</code> passed as parameter.
-     *
-     * @param message the message object to log.
-     * @param t the exception to log, including its stack trace.
-     */
-    void warn(String message, Throwable t);
-}
\ No newline at end of file
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/log/Log4j2Factory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/log/Log4j2Factory.java
deleted file mode 100644
index 0f56364..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/log/Log4j2Factory.java
+++ /dev/null
@@ -1,88 +0,0 @@
-package org.apache.commons.jcs.log;
-
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.message.MessageFactory;
-import org.apache.logging.log4j.message.MessageFormatMessageFactory;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-/**
- * This is a SPI factory implementation for log4j2
- */
-public class Log4j2Factory implements LogFactory
-{
-    /** Use java.text.MessageFormat for log messages */
-    private MessageFactory messageFactory = new MessageFormatMessageFactory();
-
-    /**
-     * Return the name of the Log subsystem managed by this factory
-     *
-     * @return the name of the log subsystem
-     */
-    @Override
-    public String getName()
-    {
-        return "log4j2";
-    }
-
-    /**
-     * Shutdown the logging system if the logging system supports it.
-     */
-    @Override
-    public void shutdown()
-    {
-        org.apache.logging.log4j.LogManager.shutdown();
-    }
-
-    /**
-     * Returns a Log using the fully qualified name of the Class as the Log
-     * name.
-     *
-     * @param clazz
-     *            The Class whose name should be used as the Log name. If null
-     *            it will default to the calling class.
-     * @return The Log.
-     * @throws UnsupportedOperationException
-     *             if {@code clazz} is {@code null} and the calling class cannot
-     *             be determined.
-     */
-    @Override
-    public Log getLog(final Class<?> clazz)
-    {
-        Logger logger = org.apache.logging.log4j.LogManager.getLogger(clazz, messageFactory);
-        return new Log4j2LogAdapter(logger);
-    }
-
-    /**
-     * Returns a Log with the specified name.
-     *
-     * @param name
-     *            The logger name. If null the name of the calling class will be
-     *            used.
-     * @return The Log.
-     * @throws UnsupportedOperationException
-     *             if {@code name} is {@code null} and the calling class cannot
-     *             be determined.
-     */
-    @Override
-    public Log getLog(final String name)
-    {
-        Logger logger = org.apache.logging.log4j.LogManager.getLogger(name, messageFactory);
-        return new Log4j2LogAdapter(logger);
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/log/Log4j2LogAdapter.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/log/Log4j2LogAdapter.java
deleted file mode 100644
index 2f437fe..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/log/Log4j2LogAdapter.java
+++ /dev/null
@@ -1,531 +0,0 @@
-package org.apache.commons.jcs.log;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-import java.util.function.Supplier;
-
-import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.Logger;
-
-/**
- * This is a wrapper around the <code>org.apache.logging.log4j.Logger</code> implementing our own
- * <code>Log</code> interface.
- */
-public class Log4j2LogAdapter implements Log
-{
-    private Logger logger;
-
-    /**
-     * Construct a Log4j Logger wrapper
-     *
-     * @param logger the log4j Logger
-     */
-    public Log4j2LogAdapter(Logger logger)
-    {
-        super();
-        this.logger = logger;
-    }
-
-    private void log(Level level, String message, Supplier<?>... paramSuppliers)
-    {
-        if (logger.isEnabled(level))
-        {
-            if (paramSuppliers == null)
-            {
-                logger.log(level, message);
-            }
-            else
-            {
-                switch (paramSuppliers.length)
-                {
-                    case 1: logger.log(level, message, paramSuppliers[0].get());
-                            break;
-                    case 2: logger.log(level, message, paramSuppliers[0].get(),
-                            paramSuppliers[1].get());
-                            break;
-                    case 3: logger.log(level, message, paramSuppliers[0].get(),
-                            paramSuppliers[1].get(), paramSuppliers[2].get());
-                            break;
-                    case 4: logger.log(level, message, paramSuppliers[0].get(),
-                            paramSuppliers[1].get(), paramSuppliers[2].get(),
-                            paramSuppliers[3].get());
-                            break;
-                    case 5: logger.log(level, message, paramSuppliers[0].get(),
-                            paramSuppliers[1].get(), paramSuppliers[2].get(),
-                            paramSuppliers[3].get(), paramSuppliers[4].get());
-                            break;
-                    default: logger.log(level, message, paramSuppliers[0].get(),
-                            paramSuppliers[1].get(), paramSuppliers[2].get(),
-                            paramSuppliers[3].get(), paramSuppliers[4].get(),
-                            paramSuppliers[5].get());
-                            break;
-                }
-            }
-        }
-    }
-
-    /**
-     * Logs a message object with the DEBUG level.
-     *
-     * @param message the message string to log.
-     */
-    @Override
-    public void debug(String message)
-    {
-        logger.debug(message);
-    }
-
-    /**
-     * Logs a message object with the DEBUG level.
-     *
-     * @param message the message object to log.
-     */
-    @Override
-    public void debug(Object message)
-    {
-        logger.debug(message);
-    }
-
-    /**
-     * Logs a message with parameters at the DEBUG level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param params parameters to the message.
-     */
-    @Override
-    public void debug(String message, Object... params)
-    {
-        logger.debug(message, params);
-    }
-
-    /**
-     * Logs a message with parameters which are only to be constructed if the
-     * logging level is the DEBUG level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param paramSuppliers An array of functions, which when called, produce
-     *        the desired log message parameters.
-     */
-    @Override
-    public void debug(String message, Supplier<?>... paramSuppliers)
-    {
-        log(Level.DEBUG, message, paramSuppliers);
-    }
-
-    /**
-     * Logs a message at the DEBUG level including the stack trace of the {@link Throwable}
-     * <code>t</code> passed as parameter.
-     *
-     * @param message the message to log.
-     * @param t the exception to log, including its stack trace.
-     */
-    @Override
-    public void debug(String message, Throwable t)
-    {
-        logger.debug(message, t);
-    }
-
-    /**
-     * Logs a message object with the ERROR level.
-     *
-     * @param message the message string to log.
-     */
-    @Override
-    public void error(String message)
-    {
-        logger.error(message);
-    }
-
-    /**
-     * Logs a message object with the ERROR level.
-     *
-     * @param message the message object to log.
-     */
-    @Override
-    public void error(Object message)
-    {
-        logger.error(message);
-    }
-
-    /**
-     * Logs a message with parameters at the ERROR level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param params parameters to the message.
-     */
-    @Override
-    public void error(String message, Object... params)
-    {
-        logger.error(message, params);
-    }
-
-    /**
-     * Logs a message with parameters which are only to be constructed if the
-     * logging level is the ERROR level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param paramSuppliers An array of functions, which when called, produce
-     *        the desired log message parameters.
-     */
-    @Override
-    public void error(String message, Supplier<?>... paramSuppliers)
-    {
-        log(Level.ERROR, message, paramSuppliers);
-    }
-
-    /**
-     * Logs a message at the ERROR level including the stack trace of the {@link Throwable}
-     * <code>t</code> passed as parameter.
-     *
-     * @param message the message object to log.
-     * @param t the exception to log, including its stack trace.
-     */
-    @Override
-    public void error(String message, Throwable t)
-    {
-        logger.error(message, t);
-    }
-
-    /**
-     * Logs a message object with the FATAL level.
-     *
-     * @param message the message string to log.
-     */
-    @Override
-    public void fatal(String message)
-    {
-        logger.fatal(message);
-    }
-
-    /**
-     * Logs a message object with the FATAL level.
-     *
-     * @param message the message object to log.
-     */
-    @Override
-    public void fatal(Object message)
-    {
-        logger.fatal(message);
-    }
-
-    /**
-     * Logs a message with parameters at the FATAL level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param params parameters to the message.
-     */
-    @Override
-    public void fatal(String message, Object... params)
-    {
-        logger.fatal(message, params);
-    }
-
-    /**
-     * Logs a message with parameters which are only to be constructed if the
-     * logging level is the FATAL level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param paramSuppliers An array of functions, which when called, produce
-     *        the desired log message parameters.
-     */
-    @Override
-    public void fatal(String message, Supplier<?>... paramSuppliers)
-    {
-        log(Level.FATAL, message, paramSuppliers);
-    }
-
-    /**
-     * Logs a message at the FATAL level including the stack trace of the {@link Throwable}
-     * <code>t</code> passed as parameter.
-     *
-     * @param message the message object to log.
-     * @param t the exception to log, including its stack trace.
-     */
-    @Override
-    public void fatal(String message, Throwable t)
-    {
-        logger.fatal(message, t);
-    }
-
-    /**
-     * Gets the logger name.
-     *
-     * @return the logger name.
-     */
-    @Override
-    public String getName()
-    {
-        return logger.getName();
-    }
-
-    /**
-     * Logs a message object with the INFO level.
-     *
-     * @param message the message string to log.
-     */
-    @Override
-    public void info(String message)
-    {
-        logger.info(message);
-    }
-
-    /**
-     * Logs a message object with the INFO level.
-     *
-     * @param message the message object to log.
-     */
-    @Override
-    public void info(Object message)
-    {
-        logger.info(message);
-    }
-
-    /**
-     * Logs a message with parameters at the INFO level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param params parameters to the message.
-     */
-    @Override
-    public void info(String message, Object... params)
-    {
-        logger.info(message, params);
-    }
-
-    /**
-     * Logs a message with parameters which are only to be constructed if the
-     * logging level is the INFO level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param paramSuppliers An array of functions, which when called, produce
-     *        the desired log message parameters.
-     */
-    @Override
-    public void info(String message, Supplier<?>... paramSuppliers)
-    {
-        log(Level.INFO, message, paramSuppliers);
-    }
-
-    /**
-     * Logs a message at the INFO level including the stack trace of the {@link Throwable}
-     * <code>t</code> passed as parameter.
-     *
-     * @param message the message object to log.
-     * @param t the exception to log, including its stack trace.
-     */
-    @Override
-    public void info(String message, Throwable t)
-    {
-        logger.info(message, t);
-    }
-
-    /**
-     * Checks whether this Logger is enabled for the DEBUG Level.
-     *
-     * @return boolean - {@code true} if this Logger is enabled for level DEBUG, {@code false}
-     *         otherwise.
-     */
-    @Override
-    public boolean isDebugEnabled()
-    {
-        return logger.isDebugEnabled();
-    }
-
-    /**
-     * Checks whether this Logger is enabled for the ERROR Level.
-     *
-     * @return boolean - {@code true} if this Logger is enabled for level ERROR, {@code false}
-     *         otherwise.
-     */
-    @Override
-    public boolean isErrorEnabled()
-    {
-        return logger.isErrorEnabled();
-    }
-
-    /**
-     * Checks whether this Logger is enabled for the FATAL Level.
-     *
-     * @return boolean - {@code true} if this Logger is enabled for level FATAL, {@code false}
-     *         otherwise.
-     */
-    @Override
-    public boolean isFatalEnabled()
-    {
-        return logger.isFatalEnabled();
-    }
-
-    /**
-     * Checks whether this Logger is enabled for the INFO Level.
-     *
-     * @return boolean - {@code true} if this Logger is enabled for level INFO, {@code false}
-     *         otherwise.
-     */
-    @Override
-    public boolean isInfoEnabled()
-    {
-        return logger.isInfoEnabled();
-    }
-
-    /**
-     * Checks whether this Logger is enabled for the TRACE level.
-     *
-     * @return boolean - {@code true} if this Logger is enabled for level TRACE, {@code false}
-     *         otherwise.
-     */
-    @Override
-    public boolean isTraceEnabled()
-    {
-        return logger.isTraceEnabled();
-    }
-
-    /**
-     * Checks whether this Logger is enabled for the WARN Level.
-     *
-     * @return boolean - {@code true} if this Logger is enabled for level WARN, {@code false}
-     *         otherwise.
-     */
-    @Override
-    public boolean isWarnEnabled()
-    {
-        return logger.isWarnEnabled();
-    }
-
-    /**
-     * Logs a message object with the TRACE level.
-     *
-     * @param message the message string to log.
-     */
-    @Override
-    public void trace(String message)
-    {
-        logger.trace(message);
-    }
-
-    /**
-     * Logs a message object with the TRACE level.
-     *
-     * @param message the message object to log.
-     */
-    @Override
-    public void trace(Object message)
-    {
-        logger.trace(message);
-    }
-
-    /**
-     * Logs a message with parameters at the TRACE level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param params parameters to the message.
-     */
-    @Override
-    public void trace(String message, Object... params)
-    {
-        logger.trace(message, params);
-    }
-
-    /**
-     * Logs a message with parameters which are only to be constructed if the
-     * logging level is the TRACE level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param paramSuppliers An array of functions, which when called, produce
-     *        the desired log message parameters.
-     */
-    @Override
-    public void trace(String message, Supplier<?>... paramSuppliers)
-    {
-        log(Level.TRACE, message, paramSuppliers);
-    }
-
-    /**
-     * Logs a message at the TRACE level including the stack trace of the {@link Throwable}
-     * <code>t</code> passed as parameter.
-     *
-     * @param message the message object to log.
-     * @param t the exception to log, including its stack trace.
-     * @see #debug(String)
-     */
-    @Override
-    public void trace(String message, Throwable t)
-    {
-        logger.trace(message, t);
-    }
-
-    /**
-     * Logs a message object with the WARN level.
-     *
-     * @param message the message string to log.
-     */
-    @Override
-    public void warn(String message)
-    {
-        logger.warn(message);
-    }
-
-    /**
-     * Logs a message object with the WARN level.
-     *
-     * @param message the message object to log.
-     */
-    @Override
-    public void warn(Object message)
-    {
-        logger.warn(message);
-    }
-
-    /**
-     * Logs a message with parameters at the WARN level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param params parameters to the message.
-     */
-    @Override
-    public void warn(String message, Object... params)
-    {
-        logger.warn(message, params);
-    }
-
-    /**
-     * Logs a message with parameters which are only to be constructed if the
-     * logging level is the WARN level.
-     *
-     * @param message the message to log; the format depends on the message factory.
-     * @param paramSuppliers An array of functions, which when called, produce
-     *        the desired log message parameters.
-     */
-    @Override
-    public void warn(String message, Supplier<?>... paramSuppliers)
-    {
-        log(Level.WARN, message, paramSuppliers);
-    }
-
-    /**
-     * Logs a message at the WARN level including the stack trace of the {@link Throwable}
-     * <code>t</code> passed as parameter.
-     *
-     * @param message the message object to log.
-     * @param t the exception to log, including its stack trace.
-     */
-    @Override
-    public void warn(String message, Throwable t)
-    {
-        logger.warn(message, t);
-    }
-}
\ No newline at end of file
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/log/LogFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/log/LogFactory.java
deleted file mode 100644
index c328293..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/log/LogFactory.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package org.apache.commons.jcs.log;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-/**
- * This is a SPI factory interface for specialized Log objects
- */
-public interface LogFactory
-{
-    /**
-     * The name of the root Log.
-     */
-    String ROOT_LOGGER_NAME = "";
-
-    /**
-     * Return the name of the Log subsystem managed by this factory
-     *
-     * @return the name of the log subsystem
-     */
-    String getName();
-
-    /**
-     * Shutdown the logging system if the logging system supports it.
-     */
-    void shutdown();
-
-    /**
-     * Returns a Log using the fully qualified name of the Class as the Log
-     * name.
-     *
-     * @param clazz
-     *            The Class whose name should be used as the Log name. If null
-     *            it will default to the calling class.
-     * @return The Log.
-     * @throws UnsupportedOperationException
-     *             if {@code clazz} is {@code null} and the calling class cannot
-     *             be determined.
-     */
-    Log getLog(final Class<?> clazz);
-
-    /**
-     * Returns a Log with the specified name.
-     *
-     * @param name
-     *            The logger name. If null the name of the calling class will be
-     *            used.
-     * @return The Log.
-     * @throws UnsupportedOperationException
-     *             if {@code name} is {@code null} and the calling class cannot
-     *             be determined.
-     */
-    Log getLog(final String name);
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/log/LogManager.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/log/LogManager.java
deleted file mode 100644
index 4256d12..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/log/LogManager.java
+++ /dev/null
@@ -1,139 +0,0 @@
-package org.apache.commons.jcs.log;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-import java.util.ServiceLoader;
-
-/**
- * This is a borrowed and stripped-down version of the log4j2 LogManager class.
- *
- * The anchor point for the JCS logging system. The most common usage of this
- * class is to obtain a named {@link Log}.
- */
-public class LogManager
-{
-    /**
-     * The name of log subsystem
-     */
-    private static String logSystem = null;
-
-    /**
-     * The SPI LogFactory
-     */
-    private static class LogFactoryHolder
-    {
-        static final LogFactory INSTANCE = createLogFactory();
-
-        /**
-         * Scans the classpath to find a logging implementation.
-         *
-         * @return the LogFactory
-         * @throws RuntimeException, if no factory implementation could be found
-         */
-        private static LogFactory createLogFactory()
-        {
-            ServiceLoader<LogFactory> factories = ServiceLoader.load(LogFactory.class);
-            if (LogManager.logSystem == null)
-            {
-                LogManager.logSystem = System.getProperty("jcs.logSystem", "jul");
-            }
-
-            for (LogFactory factory : factories)
-            {
-                if (logSystem.equalsIgnoreCase(factory.getName()))
-                {
-                    return factory;
-                }
-            }
-
-            throw new RuntimeException("Could not find factory implementation for log subsystem " + logSystem);
-        }
-    }
-
-    /**
-     * Set the log system. Must be called before getLog is called
-     *
-     * @param logSystem the logSystem to set
-     */
-    public static void setLogSystem(String logSystem)
-    {
-        LogManager.logSystem = logSystem;
-    }
-
-    /**
-     * Return the LogFactory
-     */
-    private static LogFactory getLogFactory()
-    {
-        return LogFactoryHolder.INSTANCE;
-    }
-
-    /**
-     * Prevents instantiation
-     */
-    protected LogManager()
-    {
-    }
-
-    /**
-     * Shutdown the logging system if the logging system supports it.
-     */
-    public static void shutdown()
-    {
-        getLogFactory().shutdown();
-    }
-
-    /**
-     * Returns a Log using the fully qualified name of the Class as the Log
-     * name.
-     *
-     * @param clazz
-     *            The Class whose name should be used as the Log name.
-     * @return The Log.
-     * @throws UnsupportedOperationException
-     *             if {@code clazz} is {@code null}
-     */
-    public static Log getLog(final Class<?> clazz)
-    {
-        return getLogFactory().getLog(clazz);
-    }
-
-    /**
-     * Returns a Log with the specified name.
-     *
-     * @param name
-     *            The logger name.
-     * @return The Log.
-     * @throws UnsupportedOperationException
-     *             if {@code name} is {@code null}
-     */
-    public static Log getLog(final String name)
-    {
-        return getLogFactory().getLog(name);
-    }
-
-    /**
-     * Returns the root logger.
-     *
-     * @return the root logger, named {@link LogFactory.ROOT_LOGGER_NAME}.
-     */
-    public static Log getRootLogger()
-    {
-        return getLog(LogFactory.ROOT_LOGGER_NAME);
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/log/MessageFormatter.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/log/MessageFormatter.java
deleted file mode 100644
index f8e7fc6..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/log/MessageFormatter.java
+++ /dev/null
@@ -1,130 +0,0 @@
-package org.apache.commons.jcs.log;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-import java.text.MessageFormat;
-import java.util.Arrays;
-import java.util.IllegalFormatException;
-import java.util.function.Supplier;
-
-/**
- * Handles messages that consist of a format string conforming to
- * java.text.MessageFormat. (Borrowed from log4j2)
- */
-public class MessageFormatter
-{
-    private String messagePattern;
-    private transient Object[] parameters;
-    private transient String formattedMessage;
-    private transient Throwable throwable;
-
-    /**
-     * Constructs a message formatter.
-     *
-     * @param messagePattern
-     *            the pattern for this message format
-     * @param parameters
-     *            The objects to format
-     */
-    public MessageFormatter(final String messagePattern, final Object... parameters)
-    {
-        this.messagePattern = messagePattern;
-        this.parameters = parameters;
-        final int length = parameters == null ? 0 : parameters.length;
-        if (length > 0 && parameters[length - 1] instanceof Throwable)
-        {
-            this.throwable = (Throwable) parameters[length - 1];
-        }
-    }
-
-    /**
-     * Constructs a message formatter.
-     *
-     * @param messagePattern
-     *            the pattern for this message format
-     * @param paramSuppliers
-     *            An array of functions, which when called, produce the desired
-     *            log message parameters.
-     */
-    public MessageFormatter(final String messagePattern, final Supplier<?>... paramSuppliers)
-    {
-        this.messagePattern = messagePattern;
-        this.parameters = Arrays.stream(paramSuppliers)
-                            .map(s -> s.get())
-                            .toArray();
-
-        final int length = parameters == null ? 0 : parameters.length;
-        if (length > 0 && parameters[length - 1] instanceof Throwable)
-        {
-            this.throwable = (Throwable) parameters[length - 1];
-        }
-    }
-
-    /**
-     * Returns the formatted message.
-     *
-     * @return the formatted message.
-     */
-    public String getFormattedMessage()
-    {
-        if (formattedMessage == null)
-        {
-            formattedMessage = formatMessage(messagePattern, parameters);
-        }
-        return formattedMessage;
-    }
-
-    protected String formatMessage(final String msgPattern, final Object... args)
-    {
-        try
-        {
-            final MessageFormat temp = new MessageFormat(msgPattern);
-            return temp.format(args);
-        }
-        catch (final IllegalFormatException ife)
-        {
-            return msgPattern;
-        }
-    }
-
-    @Override
-    public String toString()
-    {
-        return getFormattedMessage();
-    }
-
-    /**
-     * Return the throwable passed to the Message.
-     *
-     * @return the Throwable.
-     */
-    public Throwable getThrowable()
-    {
-        return throwable;
-    }
-
-    /**
-     * Return true, if the parameters list contains a Throwable.
-     *
-     * @return true, if the parameters list contains a Throwable.
-     */
-    public boolean hasThrowable()
-    {
-        return throwable != null;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/access/AbstractJCSWorkerHelper.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/access/AbstractJCSWorkerHelper.java
deleted file mode 100644
index 88b97b2..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/access/AbstractJCSWorkerHelper.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package org.apache.commons.jcs.utils.access;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * This is an abstract template for JCSWorkerHelper implementations. it simple has a convenience
- * method for setting the finished flag.
- * <p>
- * @author tsavo
- */
-public abstract class AbstractJCSWorkerHelper<V> implements JCSWorkerHelper<V>
-{
-    /** finished flag. Can't we use wait notify? */
-    private final AtomicBoolean finished = new AtomicBoolean(false);
-
-    /**
-     * Default
-     */
-    public AbstractJCSWorkerHelper()
-    {
-        super();
-    }
-
-    /**
-     * @return finished
-     */
-    @Override
-    public boolean isFinished()
-    {
-        return finished.get();
-    }
-
-    /**
-     * @param isFinished
-     */
-    @Override
-    public void setFinished( boolean isFinished )
-    {
-        finished.set(isFinished);
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/access/JCSWorker.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/access/JCSWorker.java
deleted file mode 100644
index d1c2e3b..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/access/JCSWorker.java
+++ /dev/null
@@ -1,286 +0,0 @@
-package org.apache.commons.jcs.utils.access;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.access.GroupCacheAccess;
-import org.apache.commons.jcs.access.exception.CacheException;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * Utility class to encapsulate doing a piece of work, and caching the results
- * in JCS. Simply construct this class with the region name for the Cache and
- * keep a static reference to it instead of the JCS itself. Then make a new
- * org.apache.commons.jcs.utils.access.AbstractJCSWorkerHelper and implement Object
- * doWork() and do the work in there, returning the object to be cached. Then
- * call .getResult() with the key and the AbstractJCSWorkerHelper to get the
- * result of the work. If the object isn't already in the Cache,
- * AbstractJCSWorkerHelper.doWork() will get called, and the result will be put
- * into the cache. If the object is already in cache, the cached result will be
- * returned instead.
- * <p>
- * As an added bonus, multiple JCSWorkers with the same region, and key won't do
- * the work multiple times: The first JCSWorker to get started will do the work,
- * and all subsequent workers with the same region, group, and key will wait on
- * the first one and use his resulting work instead of doing the work
- * themselves.
- * <p>
- * This is ideal when the work being done is a query to the database where the
- * results may take time to be retrieved.
- * <p>
- * For example:
- *
- * <pre>
- *      public static JCSWorker cachingWorker = new JCSWorker(&quot;example region&quot;);
- *   		public Object getSomething(Serializable aKey){
- *        JCSWorkerHelper helper = new AbstractJCSWorkerHelper(){
- *          public Object doWork(){
- *            // Do some (DB?) work here which results in a list
- *            // This only happens if the cache dosn't have a item in this region for aKey
- *            // Note this is especially useful with Hibernate, which will cache indiviual
- *            // Objects, but not entire query result sets.
- *            List results = query.list();
- *            // Whatever we return here get's cached with aKey, and future calls to
- *            // getResult() on a CachedWorker with the same region and key will return that instead.
- *            return results;
- *        };
- *        List result = worker.getResult(aKey, helper);
- *      }
- * </pre>
- *
- * This is essentially the same as doing:
- *
- * <pre>
- * JCS jcs = JCS.getInstance( &quot;exampleregion&quot; );
- * List results = (List) jcs.get( aKey );
- * if ( results != null )
- * {
- *     //do the work here
- *     results = query.list();
- *     jcs.put( aKey, results );
- * }
- * </pre>
- *
- * <p>
- * But has the added benefit of the work-load sharing; under normal
- * circumstances if multiple threads all tried to do the same query at the same
- * time, the same query would happen multiple times on the database, and the
- * resulting object would get put into JCS multiple times.
- * <p>
- * @author Travis Savo
- */
-public class JCSWorker<K, V>
-{
-    /** The logger */
-    private static final Log logger = LogManager.getLog( JCSWorker.class );
-
-    /** The cache we are working with */
-    private CacheAccess<K, V> cache;
-
-    /** The cache we are working with */
-    private GroupCacheAccess<K, V> groupCache;
-
-    /**
-     * Map to hold who's doing work presently.
-     */
-    private volatile ConcurrentMap<String, JCSWorkerHelper<V>> map = new ConcurrentHashMap<>();
-
-    /**
-     * Region for the JCS cache.
-     */
-    private final String region;
-
-    /**
-     * Constructor which takes a region for the JCS cache.
-     * @param aRegion
-     *            The Region to use for the JCS cache.
-     */
-    public JCSWorker( final String aRegion )
-    {
-        region = aRegion;
-        try
-        {
-            cache = JCS.getInstance( aRegion );
-            groupCache = JCS.getGroupCacheInstance( aRegion );
-        }
-        catch ( CacheException e )
-        {
-            throw new RuntimeException( e.getMessage() );
-        }
-    }
-
-    /**
-     * Getter for the region of the JCS Cache.
-     * @return The JCS region in which the result will be cached.
-     */
-    public String getRegion()
-    {
-        return region;
-    }
-
-    /**
-     * Gets the cached result for this region/key OR does the work and caches
-     * the result, returning the result. If the result has not been cached yet,
-     * this calls doWork() on the JCSWorkerHelper to do the work and cache the
-     * result. This is also an opportunity to do any post processing of the
-     * result in your CachedWorker implementation.
-     * @param aKey
-     *            The key to get/put with on the Cache.
-     * @param aWorker
-     *            The JCSWorkerHelper implementing Object doWork(). This gets
-     *            called if the cache get misses, and the result is put into
-     *            cache.
-     * @return The result of doing the work, or the cached result.
-     * @throws Exception
-     *             Throws an exception if anything goes wrong while doing the
-     *             work.
-     */
-    public V getResult( K aKey, JCSWorkerHelper<V> aWorker )
-        throws Exception
-    {
-        return run( aKey, null, aWorker );
-    }
-
-    /**
-     * Gets the cached result for this region/key OR does the work and caches
-     * the result, returning the result. If the result has not been cached yet,
-     * this calls doWork() on the JCSWorkerHelper to do the work and cache the
-     * result. This is also an opportunity to do any post processing of the
-     * result in your CachedWorker implementation.
-     * @param aKey
-     *            The key to get/put with on the Cache.
-     * @param aGroup
-     *            The cache group to put the result in.
-     * @param aWorker
-     *            The JCSWorkerHelper implementing Object doWork(). This gets
-     *            called if the cache get misses, and the result is put into
-     *            cache.
-     * @return The result of doing the work, or the cached result.
-     * @throws Exception
-     *             Throws an exception if anything goes wrong while doing the
-     *             work.
-     */
-    public V getResult( K aKey, String aGroup, JCSWorkerHelper<V> aWorker )
-        throws Exception
-    {
-        return run( aKey, aGroup, aWorker );
-    }
-
-    /**
-     * Try and get the object from the cache, and if it's not there, do the work
-     * and cache it. This also ensures that only one CachedWorker is doing the
-     * work and subsequent calls to a CachedWorker with identical
-     * region/key/group will wait on the results of this call. It will call the
-     * JCSWorkerHelper.doWork() if the cache misses, and will put the result.
-     * @param aKey
-     * @param aGroup
-     * @param aHelper
-     * @return Either the result of doing the work, or the cached result.
-     * @throws Exception
-     *             If something goes wrong while doing the work, throw an
-     *             exception.
-     */
-    private V run( K aKey, String aGroup, JCSWorkerHelper<V> aHelper )
-        throws Exception
-    {
-        V result = null;
-        // long start = 0;
-        // long dbTime = 0;
-        JCSWorkerHelper<V> helper = map.putIfAbsent(getRegion() + aKey, aHelper);
-
-        if ( helper != null )
-        {
-            synchronized ( helper )
-            {
-                logger.debug( "Found a worker already doing this work ({0}:{1}).",
-                        () -> getRegion(), () -> aKey );
-                while ( !helper.isFinished() )
-                {
-                    try
-                    {
-                        helper.wait();
-                    }
-                    catch (InterruptedException e)
-                    {
-                        // expected
-                    }
-                }
-                logger.debug( "Another thread finished our work for us. Using "
-                        + "those results instead. ({0}:{1}).",
-                        () -> getRegion(), () -> aKey );
-            }
-        }
-        // Do the work
-        try
-        {
-            logger.debug( "{0} is doing the work.", () -> getRegion() );
-
-            // Try to get the item from the cache
-            if ( aGroup != null )
-            {
-                result = groupCache.getFromGroup( aKey, aGroup );
-            }
-            else
-            {
-                result = cache.get( aKey );
-            }
-            // If the cache dosn't have it, do the work.
-            if ( result == null )
-            {
-                result = aHelper.doWork();
-                logger.debug( "Work Done, caching: key:{0}, group:{1}, result:{2}.",
-                        aKey, aGroup, result );
-                // Stick the result of the work in the cache.
-                if ( aGroup != null )
-                {
-                    groupCache.putInGroup( aKey, aGroup, result );
-                }
-                else
-                {
-                    cache.put( aKey, result );
-                }
-            }
-            // return the result
-            return result;
-        }
-        finally
-        {
-            logger.debug( "{0}:{1} entered finally.", () -> getRegion(),
-                    () -> aKey );
-
-            // Remove ourselves as the worker.
-            if ( helper == null )
-            {
-                map.remove( getRegion() + aKey );
-            }
-            synchronized ( aHelper )
-            {
-                aHelper.setFinished( true );
-                // Wake everyone waiting on us
-                aHelper.notifyAll();
-            }
-        }
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/access/JCSWorkerHelper.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/access/JCSWorkerHelper.java
deleted file mode 100644
index c20ed7b..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/access/JCSWorkerHelper.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package org.apache.commons.jcs.utils.access;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * Interface for doing a piece of work which is expected to be cached. This is
- * meant to be used in conjunction with JCSWorker.
- * <p>
- * Implement doWork() to return the work being done. isFinished() should return
- * false until setFinished(true) is called, after which time it should return
- * true.
- * <p>
- * @author tsavo
- */
-public interface JCSWorkerHelper<V>
-{
-    /**
-     * Tells us whether or not the work has been completed. This will be called
-     * automatically by JCSWorker. You should not call it yourself.
-     * <p>
-     * @return True if the work has already been done, otherwise false.
-     */
-    boolean isFinished();
-
-    /**
-     * Sets whether or not the work has been done.
-     * <p>
-     * @param isFinished
-     *            True if the work has already been done, otherwise false.
-     */
-    void setFinished( boolean isFinished );
-
-    /**
-     * The method to implement to do the work that should be cached. JCSWorker
-     * will call this itself! You should not call this directly.
-     * <p>
-     * @return The result of doing the work to be cached.
-     * @throws Exception
-     *             If anything goes wrong while doing the work, an Exception
-     *             should be thrown.
-     */
-    V doWork() throws Exception;
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/config/OptionConverter.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/config/OptionConverter.java
deleted file mode 100644
index 1a9dab0..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/config/OptionConverter.java
+++ /dev/null
@@ -1,423 +0,0 @@
-package org.apache.commons.jcs.utils.config;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.Properties;
-
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * This class is based on the log4j class org.apache.log4j.helpers.OptionConverter that was made by
- * Ceki G&uuml;lc&uuml; Simon Kitching; Avy Sharell (sharell@online.fr) Anders Kristensen Matthieu
- * Verbert (mve@zurich.ibm.com) A convenience class to convert property values to specific types.
- */
-public class OptionConverter
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog( OptionConverter.class );
-
-    /** System property delimter */
-    private static final String DELIM_START = "${";
-
-    /** System property delimter */
-    private static final char DELIM_STOP = '}';
-
-    /** System property delimter start length */
-    private static final int DELIM_START_LEN = 2;
-
-    /** System property delimter end length */
-    private static final int DELIM_STOP_LEN = 1;
-
-    /** No instances please. */
-    private OptionConverter()
-    {
-        super();
-    }
-
-    /**
-     * Combines two arrays.
-     * @param l
-     * @param r
-     * @return String[]
-     */
-    public static String[] concatanateArrays( String[] l, String[] r )
-    {
-        int len = l.length + r.length;
-        String[] a = new String[len];
-
-        System.arraycopy( l, 0, a, 0, l.length );
-        System.arraycopy( r, 0, a, l.length, r.length );
-
-        return a;
-    }
-
-    /**
-     * Escapes special characters.
-     *
-     * @param s
-     * @return String
-     */
-    public static String convertSpecialChars( String s )
-    {
-        char c;
-        int len = s.length();
-        StringBuilder sb = new StringBuilder( len );
-
-        int i = 0;
-        while ( i < len )
-        {
-            c = s.charAt( i++ );
-            if ( c == '\\' )
-            {
-                c = s.charAt( i++ );
-                if ( c == 'n' )
-                {
-                    c = '\n';
-                }
-                else if ( c == 'r' )
-                {
-                    c = '\r';
-                }
-                else if ( c == 't' )
-                {
-                    c = '\t';
-                }
-                else if ( c == 'f' )
-                {
-                    c = '\f';
-                }
-                else if ( c == '\b' )
-                {
-                    c = '\b';
-                }
-                else if ( c == '\"' )
-                {
-                    c = '\"';
-                }
-                else if ( c == '\'' )
-                {
-                    c = '\'';
-                }
-                else if ( c == '\\' )
-                {
-                    c = '\\';
-                }
-            }
-            sb.append( c );
-        }
-        return sb.toString();
-    }
-
-    /**
-     * Very similar to <code>System.getProperty</code> except that the {@link SecurityException} is
-     * hidden.
-     * @param key The key to search for.
-     * @param def The default value to return.
-     * @return the string value of the system property, or the default value if there is no property
-     *         with that key.
-     * @since 1.1
-     */
-
-    public static String getSystemProperty( String key, String def )
-    {
-        try
-        {
-            return System.getProperty( key, def );
-        }
-        catch ( Throwable e )
-        {
-            // MS-Java throws com.ms.security.SecurityExceptionEx
-            log.debug( "Was not allowed to read system property \"{0}\".", key );
-            return def;
-        }
-    }
-
-    /**
-     * Creates an object for the className value of the key.
-     *
-     * @param props
-     * @param key
-     * @param defaultValue
-     * @return Object that was created
-     */
-    public static <T> T instantiateByKey( Properties props, String key, T defaultValue )
-    {
-
-        // Get the value of the property in string form
-        String className = findAndSubst( key, props );
-        if ( className == null )
-        {
-            log.trace( "Could not find value for key {0}", key );
-            return defaultValue;
-        }
-        // Trim className to avoid trailing spaces that cause problems.
-        return OptionConverter.instantiateByClassName( className.trim(), defaultValue );
-    }
-
-    /**
-     * If <code>value</code> is "true", then <code>true</code> is returned. If <code>value</code> is
-     * "false", then <code>true</code> is returned. Otherwise, <code>default</code> is returned.
-     *
-     * Case of value is unimportant.
-     * @param value
-     * @param defaultValue
-     * @return Object
-     */
-    public static boolean toBoolean( String value, boolean defaultValue )
-    {
-        if ( value == null )
-        {
-            return defaultValue;
-        }
-        String trimmedVal = value.trim();
-        if ( "true".equalsIgnoreCase( trimmedVal ) )
-        {
-            return true;
-        }
-        if ( "false".equalsIgnoreCase( trimmedVal ) )
-        {
-            return false;
-        }
-        return defaultValue;
-    }
-
-    /**
-     * Converts to int.
-     *
-     * @param value
-     * @param defaultValue
-     * @return int
-     */
-    public static int toInt( String value, int defaultValue )
-    {
-        if ( value != null )
-        {
-            String s = value.trim();
-            try
-            {
-                return Integer.parseInt(s);
-            }
-            catch ( NumberFormatException e )
-            {
-                log.error( "[{0}] is not in proper int form.", s, e );
-            }
-        }
-        return defaultValue;
-    }
-
-    /**
-     * @param value
-     * @param defaultValue
-     * @return long
-     */
-    public static long toFileSize( String value, long defaultValue )
-    {
-        if ( value == null )
-        {
-            return defaultValue;
-        }
-
-        String s = value.trim().toUpperCase();
-        long multiplier = 1;
-        int index;
-
-        if ( ( index = s.indexOf( "KB" ) ) != -1 )
-        {
-            multiplier = 1024;
-            s = s.substring( 0, index );
-        }
-        else if ( ( index = s.indexOf( "MB" ) ) != -1 )
-        {
-            multiplier = 1024 * 1024;
-            s = s.substring( 0, index );
-        }
-        else if ( ( index = s.indexOf( "GB" ) ) != -1 )
-        {
-            multiplier = 1024 * 1024 * 1024;
-            s = s.substring( 0, index );
-        }
-        if ( s != null )
-        {
-            try
-            {
-                return Long.parseLong(s) * multiplier;
-            }
-            catch ( NumberFormatException e )
-            {
-                log.error( "[{0}] is not in proper int form.", s);
-                log.error( "[{0}] not in expected format", value, e );
-            }
-        }
-        return defaultValue;
-    }
-
-    /**
-     * Find the value corresponding to <code>key</code> in <code>props</code>. Then perform variable
-     * substitution on the found value.
-     *
-     * @param key
-     * @param props
-     * @return substituted string
-     */
-
-    public static String findAndSubst( String key, Properties props )
-    {
-        String value = props.getProperty( key );
-        if ( value == null )
-        {
-            return null;
-        }
-
-        try
-        {
-            return substVars( value, props );
-        }
-        catch ( IllegalArgumentException e )
-        {
-            log.error( "Bad option value [{0}]", value, e );
-            return value;
-        }
-    }
-
-    /**
-     * Instantiate an object given a class name. Check that the <code>className</code> is a subclass
-     * of <code>superClass</code>. If that test fails or the object could not be instantiated, then
-     * <code>defaultValue</code> is returned.
-     *
-     * @param className The fully qualified class name of the object to instantiate.
-     * @param defaultValue The object to return in case of non-fulfillment
-     * @return instantiated object
-     */
-
-    public static <T> T instantiateByClassName( String className, T defaultValue )
-    {
-        if ( className != null )
-        {
-            try
-            {
-                Class<?> classObj = Class.forName( className );
-                Object o = classObj.newInstance();
-
-                try
-                {
-                    @SuppressWarnings("unchecked") // CCE catched
-                    T t = (T) o;
-                    return t;
-                }
-                catch (ClassCastException e)
-                {
-                    log.error( "A \"{0}\" object is not assignable to the "
-                            + "generic variable.", className );
-                    return defaultValue;
-                }
-            }
-            catch ( ClassNotFoundException | InstantiationException | IllegalAccessException e )
-            {
-                log.error( "Could not instantiate class [{0}]", className, e );
-            }
-        }
-        return defaultValue;
-    }
-
-    /**
-     * Perform variable substitution in string <code>val</code> from the values of keys found in the
-     * system properties.
-     *
-     * The variable substitution delimiters are <b>${ </b> and <b>} </b>.
-     *
-     * For example, if the System properties contains "key=value", then the call
-     *
-     * <pre>
-     * String s = OptionConverter.substituteVars( &quot;Value of key is ${key}.&quot; );
-     * </pre>
-     *
-     * will set the variable <code>s</code> to "Value of key is value.".
-     *
-     * If no value could be found for the specified key, then the <code>props</code> parameter is
-     * searched, if the value could not be found there, then substitution defaults to the empty
-     * string.
-     *
-     * For example, if system properties contains no value for the key "inexistentKey", then the call
-     *
-     * <pre>
-     * String s = OptionConverter.subsVars( &quot;Value of inexistentKey is [${inexistentKey}]&quot; );
-     * </pre>
-     *
-     * will set <code>s</code> to "Value of inexistentKey is []"
-     * <p>
-     * An {@link java.lang.IllegalArgumentException}is thrown if <code>val</code> contains a start
-     * delimiter "${" which is not balanced by a stop delimiter "}".
-     * </p>
-     * <p>
-     * <b>Author </b> Avy Sharell
-     * </p>
-     * @param val The string on which variable substitution is performed.
-     * @param props
-     * @return String
-     * @throws IllegalArgumentException if <code>val</code> is malformed.
-     */
-
-    public static String substVars( String val, Properties props )
-        throws IllegalArgumentException
-    {
-        StringBuilder sbuf = new StringBuilder();
-
-        int i = 0;
-        int j;
-        int k;
-
-        while ( true )
-        {
-            j = val.indexOf( DELIM_START, i );
-            if ( j == -1 )
-            {
-                if ( i == 0 )
-                {
-                    return val;
-                }
-                sbuf.append( val.substring( i, val.length() ) );
-                return sbuf.toString();
-            }
-            sbuf.append( val.substring( i, j ) );
-            k = val.indexOf( DELIM_STOP, j );
-            if ( k == -1 )
-            {
-                throw new IllegalArgumentException( '"' + val + "\" has no closing brace. Opening brace at position "
-                    + j + '.' );
-            }
-            j += DELIM_START_LEN;
-            String key = val.substring( j, k );
-            // first try in System properties
-            String replacement = getSystemProperty( key, null );
-            // then try props parameter
-            if ( replacement == null && props != null )
-            {
-                replacement = props.getProperty( key );
-            }
-
-            if ( replacement != null )
-            {
-                sbuf.append( replacement );
-            }
-            i = k + DELIM_STOP_LEN;
-        }
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/config/PropertySetter.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/config/PropertySetter.java
deleted file mode 100644
index e188bd8..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/config/PropertySetter.java
+++ /dev/null
@@ -1,299 +0,0 @@
-package org.apache.commons.jcs.utils.config;
-
-import java.beans.BeanInfo;
-import java.beans.IntrospectionException;
-import java.beans.Introspector;
-import java.beans.PropertyDescriptor;
-import java.io.File;
-import java.lang.reflect.Method;
-import java.util.Enumeration;
-import java.util.Properties;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * This class is based on the log4j class org.apache.log4j.config.PropertySetter that was made by
- * Anders Kristensen
- * <p>
- * General purpose Object property setter. Clients repeatedly invokes {@link #setProperty
- * setProperty(name,value)} in order to invoke setters on the Object specified in the constructor.
- * This class relies on the JavaBeans {@link Introspector}to analyze the given Object Class using
- * reflection.
- * <p>
- * Usage:
- *
- * <pre>
- * PropertySetter ps = new PropertySetter( anObject );
- * ps.set( &quot;name&quot;, &quot;Joe&quot; );
- * ps.set( &quot;age&quot;, &quot;32&quot; );
- * ps.set( &quot;isMale&quot;, &quot;true&quot; );
- * </pre>
- *
- * will cause the invocations anObject.setName("Joe"), anObject.setAge(32), and setMale(true) if
- * such methods exist with those signatures. Otherwise an {@link IntrospectionException}are thrown.
- */
-public class PropertySetter
-{
-    /** Logger */
-    private static final Log log = LogManager.getLog( PropertySetter.class );
-
-    /** Description of the Field */
-    private final Object obj;
-
-    /** Description of the Field */
-    private PropertyDescriptor[] props;
-
-    /**
-     * Create a new PropertySetter for the specified Object. This is done in preparation for invoking
-     * {@link #setProperty}one or more times.
-     * @param obj the object for which to set properties
-     */
-    public PropertySetter( Object obj )
-    {
-        this.obj = obj;
-    }
-
-    /**
-     * Uses JavaBeans {@link Introspector}to compute setters of object to be configured.
-     */
-    protected void introspect()
-    {
-        try
-        {
-            BeanInfo bi = Introspector.getBeanInfo( obj.getClass() );
-            props = bi.getPropertyDescriptors();
-        }
-        catch ( IntrospectionException ex )
-        {
-            log.error( "Failed to introspect {0}", obj, ex );
-            props = new PropertyDescriptor[0];
-        }
-    }
-
-    /**
-     * Set the properties of an object passed as a parameter in one go. The <code>properties</code>
-     * are parsed relative to a <code>prefix</code>.
-     * <p>
-     * @param obj The object to configure.
-     * @param properties A java.util.Properties containing keys and values.
-     * @param prefix Only keys having the specified prefix will be set.
-     */
-    public static void setProperties( Object obj, Properties properties, String prefix )
-    {
-        new PropertySetter( obj ).setProperties( properties, prefix );
-    }
-
-    /**
-     * Set the properties for the object that match the <code>prefix</code> passed as parameter.
-     * <p>
-     * @param properties The new properties value
-     * @param prefix The new properties value
-     */
-    public void setProperties( Properties properties, String prefix )
-    {
-        int len = prefix.length();
-
-        for ( Enumeration<?> e = properties.propertyNames(); e.hasMoreElements(); )
-        {
-            String key = (String) e.nextElement();
-
-            // handle only properties that start with the desired prefix.
-            if ( key.startsWith( prefix ) )
-            {
-
-                // ignore key if it contains dots after the prefix
-                if ( key.indexOf( '.', len + 1 ) > 0 )
-                {
-                    //System.err.println("----------Ignoring---["+key
-                    //	     +"], prefix=["+prefix+"].");
-                    continue;
-                }
-
-                String value = OptionConverter.findAndSubst( key, properties );
-                key = key.substring( len );
-
-                setProperty( key, value );
-            }
-        }
-
-    }
-
-    /**
-     * Set a property on this PropertySetter's Object. If successful, this method will invoke a
-     * setter method on the underlying Object. The setter is the one for the specified property name
-     * and the value is determined partly from the setter argument type and partly from the value
-     * specified in the call to this method.
-     * <p>
-     * If the setter expects a String no conversion is necessary. If it expects an int, then an
-     * attempt is made to convert 'value' to an int using Integer.valueOf(value). If the setter expects
-     * a boolean, the conversion is by Boolean.valueOf(value).
-     * @param name name of the property
-     * @param value String value of the property
-     */
-
-    public void setProperty( String name, String value )
-    {
-        if ( value == null )
-        {
-            return;
-        }
-
-        name = Introspector.decapitalize( name );
-        PropertyDescriptor prop = getPropertyDescriptor( name );
-
-        //log.debug("---------Key: "+name+", type="+prop.getPropertyType());
-
-        if ( prop == null )
-        {
-            log.warn( "No such property [{0}] in {1}.", name, obj.getClass().getName() );
-        }
-        else
-        {
-            try
-            {
-                setProperty( prop, name, value );
-            }
-            catch ( PropertySetterException ex )
-            {
-                log.warn( "Failed to set property {0} to value \"{1}\".", name, value, ex );
-            }
-        }
-    }
-
-    /**
-     * Set the named property given a {@link PropertyDescriptor}.
-     * @param prop A PropertyDescriptor describing the characteristics of the property to set.
-     * @param name The named of the property to set.
-     * @param value The value of the property.
-     * @throws PropertySetterException
-     */
-
-    public void setProperty( PropertyDescriptor prop, String name, String value )
-        throws PropertySetterException
-    {
-        Method setter = prop.getWriteMethod();
-        if ( setter == null )
-        {
-            throw new PropertySetterException( "No setter for property" );
-        }
-        Class<?>[] paramTypes = setter.getParameterTypes();
-        if ( paramTypes.length != 1 )
-        {
-            throw new PropertySetterException( "#params for setter != 1" );
-        }
-
-        Object arg;
-        try
-        {
-            arg = convertArg( value, paramTypes[0] );
-        }
-        catch ( Throwable t )
-        {
-            throw new PropertySetterException( "Conversion to type [" + paramTypes[0] + "] failed. Reason: " + t );
-        }
-        if ( arg == null )
-        {
-            throw new PropertySetterException( "Conversion to type [" + paramTypes[0] + "] failed." );
-        }
-        log.debug( "Setting property [{0}] to [{1}].", name, arg );
-        try
-        {
-            setter.invoke( obj, new Object[] { arg } );
-        }
-        catch ( Exception ex )
-        {
-            throw new PropertySetterException( ex );
-        }
-    }
-
-    /**
-     * Convert <code>val</code> a String parameter to an object of a given type.
-     * @param val
-     * @param type
-     * @return Object
-     */
-    protected Object convertArg( String val, Class<?> type )
-    {
-        if ( val == null )
-        {
-            return null;
-        }
-
-        String v = val.trim();
-        if ( String.class.isAssignableFrom( type ) )
-        {
-            return val;
-        }
-        else if ( Integer.TYPE.isAssignableFrom( type ) )
-        {
-            return Integer.valueOf( v );
-        }
-        else if ( Long.TYPE.isAssignableFrom( type ) )
-        {
-            return Long.valueOf( v );
-        }
-        else if ( Boolean.TYPE.isAssignableFrom( type ) )
-        {
-            if ( "true".equalsIgnoreCase( v ) )
-            {
-                return Boolean.TRUE;
-            }
-            else if ( "false".equalsIgnoreCase( v ) )
-            {
-                return Boolean.FALSE;
-            }
-        }
-        else if( type.isEnum() )
-        {
-            Enum<?> en = Enum.valueOf(type.asSubclass(Enum.class), v );
-            return en;
-        }
-        else if ( File.class.isAssignableFrom( type ) )
-        {
-            return new File( v );
-        }
-        return null;
-    }
-
-    /**
-     * Gets the propertyDescriptor attribute of the PropertySetter object
-     * @param name
-     * @return The propertyDescriptor value
-     */
-    protected PropertyDescriptor getPropertyDescriptor( String name )
-    {
-        if ( props == null )
-        {
-            introspect();
-        }
-
-        for ( int i = 0; i < props.length; i++ )
-        {
-            if ( name.equals( props[i].getName() ) )
-            {
-                return props[i];
-            }
-        }
-        return null;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/config/PropertySetterException.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/config/PropertySetterException.java
deleted file mode 100644
index 0898a54..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/config/PropertySetterException.java
+++ /dev/null
@@ -1,75 +0,0 @@
-package org.apache.commons.jcs.utils.config;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * This class is based on the log4j class org.apache.log4j.config.PropertySetter that was made by
- * Anders Kristensen
- * <p>
- * Thrown when an error is encountered whilst attempting to set a property using the
- * {@link PropertySetter}utility class.
- */
-public class PropertySetterException
-    extends Exception
-{
-    /** DOn't change */
-    private static final long serialVersionUID = -210271658004609028L;
-
-    /** Description of the Field */
-    private final Throwable rootCause;
-
-    /**
-     * Constructor for the PropertySetterException object
-     * <p>
-     * @param msg
-     */
-    public PropertySetterException( String msg )
-    {
-        super( msg );
-        this.rootCause = null;
-    }
-
-    /**
-     * Constructor for the PropertySetterException object
-     * <p>
-     * @param rootCause
-     */
-    public PropertySetterException( Throwable rootCause )
-    {
-        super();
-        this.rootCause = rootCause;
-    }
-
-    /**
-     * Returns descriptive text on the cause of this exception.
-     * <p>
-     * @return The message value
-     */
-    @Override
-    public String getMessage()
-    {
-        String msg = super.getMessage();
-        if ( msg == null && rootCause != null )
-        {
-            msg = rootCause.getMessage();
-        }
-        return msg;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/discovery/DiscoveredService.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/discovery/DiscoveredService.java
deleted file mode 100644
index cbfd737..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/discovery/DiscoveredService.java
+++ /dev/null
@@ -1,183 +0,0 @@
-package org.apache.commons.jcs.utils.discovery;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.Serializable;
-import java.util.ArrayList;
-
-/**
- * This contains info about a discovered service. These objects are stored in a set in the
- * UDPDiscoveryService.
- * <p>
- * @author Aaron Smuts
- */
-public class DiscoveredService
-    implements Serializable
-{
-    /** For serialization. Don't change. */
-    private static final long serialVersionUID = -7810164772089509751L;
-
-    /** region names */
-    private ArrayList<String> cacheNames;
-
-    /** service address */
-    private String serviceAddress;
-
-    /** service port */
-    private int servicePort;
-
-    /** last time we heard from this service? */
-    private long lastHearFromTime = 0;
-
-    /**
-     * @param cacheNames the cacheNames to set
-     */
-    public void setCacheNames( ArrayList<String> cacheNames )
-    {
-        this.cacheNames = cacheNames;
-    }
-
-    /**
-     * @return the cacheNames
-     */
-    public ArrayList<String> getCacheNames()
-    {
-        return cacheNames;
-    }
-
-    /**
-     * @param serviceAddress The serviceAddress to set.
-     */
-    public void setServiceAddress( String serviceAddress )
-    {
-        this.serviceAddress = serviceAddress;
-    }
-
-    /**
-     * @return Returns the serviceAddress.
-     */
-    public String getServiceAddress()
-    {
-        return serviceAddress;
-    }
-
-    /**
-     * @param servicePort The servicePort to set.
-     */
-    public void setServicePort( int servicePort )
-    {
-        this.servicePort = servicePort;
-    }
-
-    /**
-     * @return Returns the servicePort.
-     */
-    public int getServicePort()
-    {
-        return servicePort;
-    }
-
-    /**
-     * @param lastHearFromTime The lastHearFromTime to set.
-     */
-    public void setLastHearFromTime( long lastHearFromTime )
-    {
-        this.lastHearFromTime = lastHearFromTime;
-    }
-
-    /**
-     * @return Returns the lastHearFromTime.
-     */
-    public long getLastHearFromTime()
-    {
-        return lastHearFromTime;
-    }
-
-    /** @return hashcode based on address/port */
-	@Override
-	public int hashCode()
-	{
-		final int prime = 31;
-		int result = 1;
-		result = prime * result
-				+ ((serviceAddress == null) ? 0 : serviceAddress.hashCode());
-		result = prime * result + servicePort;
-		return result;
-	}
-
-	/**
-     * NOTE - this object is often put into sets, so equals needs to be overridden.
-     * <p>
-     * We can't use cache names as part of the equals unless we manually only use the address and
-     * port in a contains check. So that we can use normal set functionality, I've kept the cache
-     * names out.
-     * <p>
-     * @param otherArg other
-     * @return equality based on the address/port
-     */
-	@Override
-	public boolean equals(Object otherArg)
-	{
-		if (this == otherArg)
-		{
-			return true;
-		}
-		if (otherArg == null)
-		{
-			return false;
-		}
-		if (!(otherArg instanceof DiscoveredService))
-		{
-			return false;
-		}
-		DiscoveredService other = (DiscoveredService) otherArg;
-		if (serviceAddress == null)
-		{
-			if (other.serviceAddress != null)
-			{
-				return false;
-			}
-		} else if (!serviceAddress.equals(other.serviceAddress))
-		{
-			return false;
-		}
-		if (servicePort != other.servicePort)
-		{
-			return false;
-		}
-
-		return true;
-	}
-
-    /**
-     * @return string for debugging purposes.
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder buf = new StringBuilder();
-        buf.append( "\n DiscoveredService" );
-        buf.append( "\n CacheNames = [" + getCacheNames() + "]" );
-        buf.append( "\n ServiceAddress = [" + getServiceAddress() + "]" );
-        buf.append( "\n ServicePort = [" + getServicePort() + "]" );
-        buf.append( "\n LastHearFromTime = [" + getLastHearFromTime() + "]" );
-        return buf.toString();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/discovery/UDPCleanupRunner.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/discovery/UDPCleanupRunner.java
deleted file mode 100644
index 4a216cb..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/discovery/UDPCleanupRunner.java
+++ /dev/null
@@ -1,92 +0,0 @@
-package org.apache.commons.jcs.utils.discovery;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * This class periodically check the lastHeardFrom time on the services.
- * <p>
- * If they exceed the configurable limit, it removes them from the set.
- * <p>
- * @author Aaron Smuts
- */
-public class UDPCleanupRunner
-    implements Runnable
-{
-    /** log instance */
-    private static final Log log = LogManager.getLog( UDPCleanupRunner.class );
-
-    /** UDP discovery service */
-    private final UDPDiscoveryService discoveryService;
-
-    /** default for max idle time, in seconds */
-    private static final long DEFAULT_MAX_IDLE_TIME_SECONDS = 180;
-
-    /** The configured max idle time, in seconds */
-    private final long maxIdleTimeSeconds = DEFAULT_MAX_IDLE_TIME_SECONDS;
-
-    /**
-     * @param service UDPDiscoveryService
-     */
-    public UDPCleanupRunner( UDPDiscoveryService service )
-    {
-        this.discoveryService = service;
-    }
-
-    /**
-     * This goes through the list of services and removes those that we haven't heard from in longer
-     * than the max idle time.
-     * <p>
-     * @see java.lang.Runnable#run()
-     */
-    @Override
-    public void run()
-    {
-        long now = System.currentTimeMillis();
-
-        // iterate through the set
-        // it is thread safe
-        // TODO this should get a copy.  you can't simply remove from this.
-        // the listeners need to be notified.
-        Set<DiscoveredService> toRemove = new HashSet<>();
-        // can't remove via the iterator. must remove directly
-        for (DiscoveredService service : discoveryService.getDiscoveredServices())
-        {
-            if ( ( now - service.getLastHearFromTime() ) > ( maxIdleTimeSeconds * 1000 ) )
-            {
-                log.info( "Removing service, since we haven't heard from it in "
-                        + "{0} seconds. service = {1}", maxIdleTimeSeconds, service );
-                toRemove.add( service );
-            }
-        }
-
-        // remove the bad ones
-        for (DiscoveredService service : toRemove)
-        {
-            // call this so the listeners get notified
-            discoveryService.removeDiscoveredService( service );
-        }
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/discovery/UDPDiscoveryAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/discovery/UDPDiscoveryAttributes.java
deleted file mode 100644
index 1cf3962..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/discovery/UDPDiscoveryAttributes.java
+++ /dev/null
@@ -1,269 +0,0 @@
-package org.apache.commons.jcs.utils.discovery;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * Configuration properties for UDP discover service.
- * <p>
- * The service will allow out applications to find each other.
- * <p>
- * @author Aaron Smuts
- */
-public final class UDPDiscoveryAttributes
-    implements Cloneable
-{
-    /** service name */
-    private String serviceName;
-
-    /** service address */
-    private String serviceAddress;
-
-    /** service port */
-    private int servicePort;
-
-    /**
-     * false -> this service instance is not ready to receive requests. true -> ready for use
-     */
-    private boolean isDark;
-
-    /** default udp discovery address */
-    private static final String DEFAULT_UDP_DISCOVERY_ADDRESS = "228.4.5.6";
-
-    /** default udp discovery port */
-    private static final int DEFAULT_UDP_DISCOVERY_PORT = 5678;
-
-    /** udp discovery address */
-    private String udpDiscoveryAddr = DEFAULT_UDP_DISCOVERY_ADDRESS;
-
-    /** udp discovery network interface */
-    private String udpDiscoveryInterface = null;
-
-    /** udp discovery port */
-    private int udpDiscoveryPort = DEFAULT_UDP_DISCOVERY_PORT;
-
-    /** udp datagram TTL */
-    private int udpTTL = 0;
-
-    /** default delay between sending passive broadcasts */
-    private static final int DEFAULT_SEND_DELAY_SEC = 60;
-
-    /** delay between sending passive broadcasts */
-    private int sendDelaySec = DEFAULT_SEND_DELAY_SEC;
-
-    /** default amount of time before we remove services that we haven't heard from */
-    private static final int DEFAULT_MAX_IDLE_TIME_SEC = 180;
-
-    /** amount of time before we remove services that we haven't heard from */
-    private int maxIdleTimeSec = DEFAULT_MAX_IDLE_TIME_SEC;
-
-    /**
-     * @param serviceName The serviceName to set.
-     */
-    public void setServiceName( String serviceName )
-    {
-        this.serviceName = serviceName;
-    }
-
-    /**
-     * @return Returns the serviceName.
-     */
-    public String getServiceName()
-    {
-        return serviceName;
-    }
-
-    /**
-     * @param serviceAddress The serviceAddress to set.
-     */
-    public void setServiceAddress( String serviceAddress )
-    {
-        this.serviceAddress = serviceAddress;
-    }
-
-    /**
-     * @return Returns the serviceAddress.
-     */
-    public String getServiceAddress()
-    {
-        return serviceAddress;
-    }
-
-    /**
-     * @param servicePort The servicePort to set.
-     */
-    public void setServicePort( int servicePort )
-    {
-        this.servicePort = servicePort;
-    }
-
-    /**
-     * @return Returns the servicePort.
-     */
-    public int getServicePort()
-    {
-        return servicePort;
-    }
-
-    /**
-     * @param udpDiscoveryAddr The udpDiscoveryAddr to set.
-     */
-    public void setUdpDiscoveryAddr( String udpDiscoveryAddr )
-    {
-        this.udpDiscoveryAddr = udpDiscoveryAddr;
-    }
-
-    /**
-     * @return Returns the udpDiscoveryAddr.
-     */
-    public String getUdpDiscoveryAddr()
-    {
-        return udpDiscoveryAddr;
-    }
-
-    /**
-     * @param udpDiscoveryInterface The udpDiscoveryInterface to set.
-     */
-    public void setUdpDiscoveryInterface( String udpDiscoveryInterface )
-    {
-        this.udpDiscoveryInterface = udpDiscoveryInterface;
-    }
-
-    /**
-     * @return Returns the udpDiscoveryInterface.
-     */
-    public String getUdpDiscoveryInterface()
-    {
-        return udpDiscoveryInterface;
-    }
-
-    /**
-     * @param udpDiscoveryPort The udpDiscoveryPort to set.
-     */
-    public void setUdpDiscoveryPort( int udpDiscoveryPort )
-    {
-        this.udpDiscoveryPort = udpDiscoveryPort;
-    }
-
-    /**
-     * @return Returns the udpTTL.
-     */
-    public int getUdpTTL()
-    {
-        return udpTTL;
-    }
-
-    /**
-     * @param udpTTL The udpTTL to set.
-     */
-    public void setUdpTTL( int udpTTL )
-    {
-        this.udpTTL = udpTTL;
-    }
-
-    /**
-     * @return Returns the udpDiscoveryPort.
-     */
-    public int getUdpDiscoveryPort()
-    {
-        return udpDiscoveryPort;
-    }
-
-    /**
-     * @param sendDelaySec The sendDelaySec to set.
-     */
-    public void setSendDelaySec( int sendDelaySec )
-    {
-        this.sendDelaySec = sendDelaySec;
-    }
-
-    /**
-     * @return Returns the sendDelaySec.
-     */
-    public int getSendDelaySec()
-    {
-        return sendDelaySec;
-    }
-
-    /**
-     * @param maxIdleTimeSec The maxIdleTimeSec to set.
-     */
-    public void setMaxIdleTimeSec( int maxIdleTimeSec )
-    {
-        this.maxIdleTimeSec = maxIdleTimeSec;
-    }
-
-    /**
-     * @return Returns the maxIdleTimeSec.
-     */
-    public int getMaxIdleTimeSec()
-    {
-        return maxIdleTimeSec;
-    }
-
-    /**
-     * @return Returns the isDark.
-     */
-    public boolean isDark()
-    {
-        return isDark;
-    }
-
-    /**
-     * @param isDark The isDark to set.
-     */
-    public void setDark( boolean isDark )
-    {
-        this.isDark = isDark;
-    }
-
-    /** @return a clone of this object */
-    @Override
-    public UDPDiscoveryAttributes clone()
-    {
-        UDPDiscoveryAttributes attributes = new UDPDiscoveryAttributes();
-        attributes.setSendDelaySec( this.getSendDelaySec() );
-        attributes.setMaxIdleTimeSec( this.getMaxIdleTimeSec() );
-        attributes.setServiceName( this.getServiceName() );
-        attributes.setServicePort( this.getServicePort() );
-        attributes.setUdpDiscoveryAddr( this.getUdpDiscoveryAddr() );
-        attributes.setUdpDiscoveryPort( this.getUdpDiscoveryPort() );
-        attributes.setDark( this.isDark() );
-        return attributes;
-    }
-
-    /**
-     * @return string for debugging purposes.
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder buf = new StringBuilder();
-        buf.append( "\n UDPDiscoveryAttributes" );
-        buf.append( "\n ServiceName = [" + getServiceName() + "]" );
-        buf.append( "\n ServiceAddress = [" + getServiceAddress() + "]" );
-        buf.append( "\n ServicePort = [" + getServicePort() + "]" );
-        buf.append( "\n UdpDiscoveryAddr = [" + getUdpDiscoveryAddr() + "]" );
-        buf.append( "\n UdpDiscoveryPort = [" + getUdpDiscoveryPort() + "]" );
-        buf.append( "\n SendDelaySec = [" + getSendDelaySec() + "]" );
-        buf.append( "\n MaxIdleTimeSec = [" + getMaxIdleTimeSec() + "]" );
-        buf.append( "\n IsDark = [" + isDark() + "]" );
-        return buf.toString();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/discovery/UDPDiscoveryManager.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/discovery/UDPDiscoveryManager.java
deleted file mode 100644
index b24732e..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/discovery/UDPDiscoveryManager.java
+++ /dev/null
@@ -1,108 +0,0 @@
-package org.apache.commons.jcs.utils.discovery;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
-import org.apache.commons.jcs.engine.behavior.IProvideScheduler;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * This manages UDPDiscovery Services. We should end up with one service per Lateral Cache Manager
- * Instance. One service works for multiple regions. We don't want a connection for each region.
- * <p>
- * @author Aaron Smuts
- */
-public class UDPDiscoveryManager
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog( UDPDiscoveryManager.class );
-
-    /** Singleton instance */
-    private static UDPDiscoveryManager INSTANCE = new UDPDiscoveryManager();
-
-    /** Known services */
-    private final ConcurrentMap<String, UDPDiscoveryService> services = new ConcurrentHashMap<>();
-
-    /** private for singleton */
-    private UDPDiscoveryManager()
-    {
-        // noopt
-    }
-
-    /**
-     * Singleton
-     * <p>
-     * @return UDPDiscoveryManager
-     */
-    public static UDPDiscoveryManager getInstance()
-    {
-        return INSTANCE;
-    }
-
-    /**
-     * Creates a service for the address and port if one doesn't exist already.
-     * <p>
-     * We need to key this using the listener port too. TODO think of making one discovery service
-     * work for multiple types of clients.
-     * <p>
-     * @param discoveryAddress
-     * @param discoveryPort
-     * @param servicePort
-     * @param cacheMgr
-     * @return UDPDiscoveryService
-     */
-    public UDPDiscoveryService getService( String discoveryAddress, int discoveryPort, int servicePort,
-                                                        ICompositeCacheManager cacheMgr )
-    {
-        String key = discoveryAddress + ":" + discoveryPort + ":" + servicePort;
-
-        UDPDiscoveryService service = services.computeIfAbsent(key, k -> {
-            log.info( "Creating service for address:port:servicePort [{0}]", key );
-
-            UDPDiscoveryAttributes attributes = new UDPDiscoveryAttributes();
-            attributes.setUdpDiscoveryAddr( discoveryAddress );
-            attributes.setUdpDiscoveryPort( discoveryPort );
-            attributes.setServicePort( servicePort );
-
-            UDPDiscoveryService newService = new UDPDiscoveryService( attributes );
-
-            // register for shutdown notification
-            cacheMgr.registerShutdownObserver( newService );
-
-            // inject scheduler
-            if ( cacheMgr instanceof IProvideScheduler)
-            {
-                newService.setScheduledExecutorService(((IProvideScheduler)cacheMgr)
-                        .getScheduledExecutorService());
-            }
-
-            newService.startup();
-            return newService;
-        });
-
-        log.debug( "Returning service [{0}] for key [{1}]", service, key );
-
-        return service;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/discovery/UDPDiscoveryMessage.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/discovery/UDPDiscoveryMessage.java
deleted file mode 100644
index b71e6e0..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/discovery/UDPDiscoveryMessage.java
+++ /dev/null
@@ -1,166 +0,0 @@
-package org.apache.commons.jcs.utils.discovery;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.Serializable;
-import java.util.ArrayList;
-
-/**
- * The message sent by the discovery mechanism.
- */
-public class UDPDiscoveryMessage
-    implements Serializable
-{
-    /** Don't change */
-    private static final long serialVersionUID = -5332377899560951793L;
-
-    public enum BroadcastType
-    {
-        /**
-         * This is the periodic broadcast of a servers location. This type of message is also sent in
-         * response to a REQUEST_BROADCAST.
-         */
-        PASSIVE,
-
-        /**
-         * This asks recipients to broadcast their location. This is used on startup.
-         */
-        REQUEST,
-
-        /**
-         * This message instructs the receiver to remove this service from its list.
-         */
-        REMOVE
-    }
-
-    /** The message type */
-    private BroadcastType messageType = BroadcastType.PASSIVE;
-
-    /** udp port */
-    private int port = 6789;
-
-    /** UDP host */
-    private String host = "228.5.6.7";
-
-    /** Id of the requester, allows self-filtration */
-    private long requesterId;
-
-    /** Names of regions */
-    private ArrayList<String> cacheNames = new ArrayList<>();
-
-    /**
-     * @param port The port to set.
-     */
-    public void setPort( int port )
-    {
-        this.port = port;
-    }
-
-    /**
-     * @return Returns the port.
-     */
-    public int getPort()
-    {
-        return port;
-    }
-
-    /**
-     * @param host The host to set.
-     */
-    public void setHost( String host )
-    {
-        this.host = host;
-    }
-
-    /**
-     * @return Returns the host.
-     */
-    public String getHost()
-    {
-        return host;
-    }
-
-    /**
-     * @param requesterId The requesterId to set.
-     */
-    public void setRequesterId( long requesterId )
-    {
-        this.requesterId = requesterId;
-    }
-
-    /**
-     * @return Returns the requesterId.
-     */
-    public long getRequesterId()
-    {
-        return requesterId;
-    }
-
-    /**
-     * @param messageType The messageType to set.
-     */
-    public void setMessageType( BroadcastType messageType )
-    {
-        this.messageType = messageType;
-    }
-
-    /**
-     * @return Returns the messageType.
-     */
-    public BroadcastType getMessageType()
-    {
-        return messageType;
-    }
-
-    /**
-     * @param cacheNames The cacheNames to set.
-     */
-    public void setCacheNames( ArrayList<String> cacheNames )
-    {
-        this.cacheNames = cacheNames;
-    }
-
-    /**
-     * @return Returns the cacheNames.
-     */
-    public ArrayList<String> getCacheNames()
-    {
-        return cacheNames;
-    }
-
-    /**
-     * @return debugging string
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder buf = new StringBuilder();
-        buf.append( "\n host = [" + host + "]" );
-        buf.append( "\n port = [" + port + "]" );
-        buf.append( "\n requesterId = [" + requesterId + "]" );
-        buf.append( "\n messageType = [" + messageType + "]" );
-        buf.append( "\n Cache Names" );
-        for (String name : cacheNames)
-        {
-            buf.append( " cacheName = [" + name + "]" );
-        }
-        return buf.toString();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/discovery/UDPDiscoveryReceiver.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/discovery/UDPDiscoveryReceiver.java
deleted file mode 100644
index 7d2ea6a..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/discovery/UDPDiscoveryReceiver.java
+++ /dev/null
@@ -1,366 +0,0 @@
-package org.apache.commons.jcs.utils.discovery;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.net.DatagramPacket;
-import java.net.InetAddress;
-import java.net.MulticastSocket;
-import java.net.NetworkInterface;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.apache.commons.jcs.engine.CacheInfo;
-import org.apache.commons.jcs.engine.behavior.IShutdownObserver;
-import org.apache.commons.jcs.io.ObjectInputStreamClassLoaderAware;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-import org.apache.commons.jcs.utils.discovery.UDPDiscoveryMessage.BroadcastType;
-import org.apache.commons.jcs.utils.net.HostNameUtil;
-import org.apache.commons.jcs.utils.threadpool.PoolConfiguration;
-import org.apache.commons.jcs.utils.threadpool.PoolConfiguration.WhenBlockedPolicy;
-import org.apache.commons.jcs.utils.threadpool.ThreadPoolManager;
-
-/** Receives UDP Discovery messages. */
-public class UDPDiscoveryReceiver
-    implements Runnable, IShutdownObserver
-{
-    /** The log factory */
-    private static final Log log = LogManager.getLog( UDPDiscoveryReceiver.class );
-
-    /** buffer */
-    private final byte[] mBuffer = new byte[65536];
-
-    /** The socket used for communication. */
-    private MulticastSocket mSocket;
-
-    /**
-     * TODO: Consider using the threadpool manager to get this thread pool. For now place a tight
-     * restriction on the pool size
-     */
-    private static final int maxPoolSize = 2;
-
-    /** The processor */
-    private final ExecutorService pooledExecutor;
-
-    /** number of messages received. For debugging and testing. */
-    private AtomicInteger cnt = new AtomicInteger(0);
-
-    /** Service to get cache names and handle request broadcasts */
-    private final UDPDiscoveryService service;
-
-    /** Multicast address */
-    private final InetAddress multicastAddress;
-
-    /** Is it shutdown. */
-    private boolean shutdown = false;
-
-    /**
-     * Constructor for the LateralUDPReceiver object.
-     * <p>
-     * We determine out own host using InetAddress
-     *<p>
-     * @param service
-     * @param multicastInterfaceString
-     * @param multicastAddressString
-     * @param multicastPort
-     * @throws IOException
-     */
-    public UDPDiscoveryReceiver( UDPDiscoveryService service, String multicastInterfaceString,
-            String multicastAddressString, int multicastPort )
-        throws IOException
-    {
-        this.service = service;
-        this.multicastAddress = InetAddress.getByName( multicastAddressString );
-
-        // create a small thread pool to handle a barrage
-        this.pooledExecutor = ThreadPoolManager.getInstance().createPool(
-        		new PoolConfiguration(false, 0, maxPoolSize, maxPoolSize, 0,
-        		        WhenBlockedPolicy.DISCARDOLDEST, maxPoolSize),
-        		"JCS-UDPDiscoveryReceiver-", Thread.MIN_PRIORITY);
-
-        log.info( "Constructing listener, [{0}:{1}]", multicastAddress, multicastPort );
-
-        createSocket( multicastInterfaceString, multicastAddress, multicastPort );
-    }
-
-    /**
-     * Creates the socket for this class.
-     * <p>
-     * @param multicastInterfaceString
-     * @param multicastAddress
-     * @param multicastPort
-     * @throws IOException
-     */
-    private void createSocket( String multicastInterfaceString, InetAddress multicastAddress,
-            int multicastPort )
-        throws IOException
-    {
-        try
-        {
-            mSocket = new MulticastSocket( multicastPort );
-            if (log.isInfoEnabled())
-            {
-                log.info( "Joining Group: [{0}]", multicastAddress );
-            }
-
-            // Use dedicated interface if specified
-            NetworkInterface multicastInterface = null;
-            if (multicastInterfaceString != null)
-            {
-                multicastInterface = NetworkInterface.getByName(multicastInterfaceString);
-            }
-            else
-            {
-                multicastInterface = HostNameUtil.getMulticastNetworkInterface();
-            }
-            if (multicastInterface != null)
-            {
-                log.info("Using network interface {0}", multicastInterface.getDisplayName());
-                mSocket.setNetworkInterface(multicastInterface);
-            }
-
-            mSocket.joinGroup( multicastAddress );
-        }
-        catch ( IOException e )
-        {
-            log.error( "Could not bind to multicast address [{0}:{1}]", multicastAddress,
-                    multicastPort, e );
-            throw e;
-        }
-    }
-
-    /**
-     * Highly unreliable. If it is processing one message while another comes in, the second
-     * message is lost. This is for low concurrency peppering.
-     * <p>
-     * @return the object message
-     * @throws IOException
-     */
-    public Object waitForMessage()
-        throws IOException
-    {
-        final DatagramPacket packet = new DatagramPacket( mBuffer, mBuffer.length );
-        Object obj = null;
-        try
-        {
-            log.debug( "Waiting for message." );
-
-            mSocket.receive( packet );
-
-            log.debug( "Received packet from address [{0}]",
-                    () -> packet.getSocketAddress() );
-
-            try (ByteArrayInputStream byteStream = new ByteArrayInputStream(mBuffer, 0, packet.getLength());
-                 ObjectInputStream objectStream = new ObjectInputStreamClassLoaderAware(byteStream, null))
-            {
-                obj = objectStream.readObject();
-            }
-
-            if ( obj instanceof UDPDiscoveryMessage )
-            {
-            	// Ensure that the address we're supposed to send to is, indeed, the address
-            	// of the machine on the other end of this connection.  This guards against
-            	// instances where we don't exactly get the right local host address
-            	UDPDiscoveryMessage msg = (UDPDiscoveryMessage) obj;
-            	msg.setHost(packet.getAddress().getHostAddress());
-
-                log.debug( "Read object from address [{0}], object=[{1}]",
-                        packet.getSocketAddress(), obj );
-            }
-        }
-        catch ( Exception e )
-        {
-            log.error( "Error receiving multicast packet", e );
-        }
-
-        return obj;
-    }
-
-    /** Main processing method for the LateralUDPReceiver object */
-    @Override
-    public void run()
-    {
-        try
-        {
-            while ( !shutdown )
-            {
-                Object obj = waitForMessage();
-
-                cnt.incrementAndGet();
-
-                log.debug( "{0} messages received.", () -> getCnt() );
-
-                UDPDiscoveryMessage message = null;
-
-                try
-                {
-                    message = (UDPDiscoveryMessage) obj;
-                    // check for null
-                    if ( message != null )
-                    {
-                        MessageHandler handler = new MessageHandler( message );
-
-                        pooledExecutor.execute( handler );
-
-                        log.debug( "Passed handler to executor." );
-                    }
-                    else
-                    {
-                        log.warn( "message is null" );
-                    }
-                }
-                catch ( ClassCastException cce )
-                {
-                    log.warn( "Received unknown message type", cce.getMessage() );
-                }
-            } // end while
-        }
-        catch ( IOException e )
-        {
-            log.error( "Unexpected exception in UDP receiver.", e );
-            try
-            {
-                Thread.sleep( 100 );
-                // TODO consider some failure count so we don't do this
-                // forever.
-            }
-            catch ( InterruptedException e2 )
-            {
-                log.error( "Problem sleeping", e2 );
-            }
-        }
-    }
-
-    /**
-     * @param cnt The cnt to set.
-     */
-    public void setCnt( int cnt )
-    {
-        this.cnt.set(cnt);
-    }
-
-    /**
-     * @return Returns the cnt.
-     */
-    public int getCnt()
-    {
-        return cnt.get();
-    }
-
-    /**
-     * Separate thread run when a command comes into the UDPDiscoveryReceiver.
-     */
-    public class MessageHandler
-        implements Runnable
-    {
-        /** The message to handle. Passed in during construction. */
-        private UDPDiscoveryMessage message = null;
-
-        /**
-         * @param message
-         */
-        public MessageHandler( UDPDiscoveryMessage message )
-        {
-            this.message = message;
-        }
-
-        /**
-         * Process the message.
-         */
-        @SuppressWarnings("synthetic-access")
-        @Override
-        public void run()
-        {
-            // consider comparing ports here instead.
-            if ( message.getRequesterId() == CacheInfo.listenerId )
-            {
-                log.debug( "Ignoring message sent from self" );
-            }
-            else
-            {
-                log.debug( "Process message sent from another" );
-                log.debug( "Message = {0}", message );
-
-                if ( message.getHost() == null || message.getCacheNames() == null || message.getCacheNames().isEmpty() )
-                {
-                    log.debug( "Ignoring invalid message: {0}", message );
-                }
-                else
-                {
-                    processMessage();
-                }
-            }
-        }
-
-        /**
-         * Process the incoming message.
-         */
-        @SuppressWarnings("synthetic-access")
-        private void processMessage()
-        {
-            DiscoveredService discoveredService = new DiscoveredService();
-            discoveredService.setServiceAddress( message.getHost() );
-            discoveredService.setCacheNames( message.getCacheNames() );
-            discoveredService.setServicePort( message.getPort() );
-            discoveredService.setLastHearFromTime( System.currentTimeMillis() );
-
-            // if this is a request message, have the service handle it and
-            // return
-            if ( message.getMessageType() == BroadcastType.REQUEST )
-            {
-                log.debug( "Message is a Request Broadcast, will have the service handle it." );
-                service.serviceRequestBroadcast();
-                return;
-            }
-            else if ( message.getMessageType() == BroadcastType.REMOVE )
-            {
-                log.debug( "Removing service from set {0}", discoveredService );
-                service.removeDiscoveredService( discoveredService );
-            }
-            else
-            {
-                service.addOrUpdateService( discoveredService );
-            }
-        }
-    }
-
-    /** Shuts down the socket. */
-    @Override
-    public void shutdown()
-    {
-        if (!shutdown)
-        {
-            try
-            {
-                shutdown = true;
-                mSocket.leaveGroup( multicastAddress );
-                mSocket.close();
-                pooledExecutor.shutdownNow();
-            }
-            catch ( IOException e )
-            {
-                log.error( "Problem closing socket" );
-            }
-        }
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/discovery/UDPDiscoverySender.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/discovery/UDPDiscoverySender.java
deleted file mode 100644
index 6f4f9fe..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/discovery/UDPDiscoverySender.java
+++ /dev/null
@@ -1,260 +0,0 @@
-package org.apache.commons.jcs.utils.discovery;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.net.DatagramPacket;
-import java.net.InetAddress;
-import java.net.MulticastSocket;
-import java.util.ArrayList;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.CacheInfo;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-import org.apache.commons.jcs.utils.discovery.UDPDiscoveryMessage.BroadcastType;
-import org.apache.commons.jcs.utils.serialization.StandardSerializer;
-
-/**
- * This is a generic sender for the UDPDiscovery process.
- * <p>
- * @author Aaron Smuts
- */
-public class UDPDiscoverySender implements AutoCloseable
-{
-    /** The logger. */
-    private static final Log log = LogManager.getLog( UDPDiscoverySender.class );
-
-    /** The socket */
-    private final MulticastSocket localSocket;
-
-    /** The address */
-    private final InetAddress multicastAddress;
-
-    /** The port */
-    private final int multicastPort;
-
-    /** Used to serialize messages */
-    private final StandardSerializer serializer = new StandardSerializer();
-
-    /**
-     * Constructor for the UDPDiscoverySender object
-     * <p>
-     * This sender can be used to send multiple messages.
-     * <p>
-     * When you are done sending, you should destroy the socket sender.
-     * <p>
-     * @param host
-     * @param port
-     * @param udpTTL the Datagram packet time-to-live
-     * @throws IOException
-     */
-    public UDPDiscoverySender( String host, int port, int udpTTL )
-        throws IOException
-    {
-        try
-        {
-            log.debug( "Constructing socket for sender on port [{0}]", port );
-            localSocket = new MulticastSocket( port );
-            if (udpTTL > 0)
-            {
-                log.debug( "Setting datagram TTL to [{0}]", udpTTL );
-                localSocket.setTimeToLive(udpTTL);
-            }
-
-            // Remote address.
-            multicastAddress = InetAddress.getByName( host );
-        }
-        catch ( IOException e )
-        {
-            log.error( "Could not bind to multicast address [{0}]", host, e );
-            throw e;
-        }
-
-        this.multicastPort = port;
-    }
-
-    /**
-     * Closes the socket connection.
-     */
-    @Override
-    public void close()
-    {
-        if ( this.localSocket != null && !this.localSocket.isClosed() )
-        {
-            this.localSocket.close();
-        }
-    }
-
-    /**
-     * Send messages.
-     * <p>
-     * @param message
-     * @throws IOException
-     */
-    public void send( UDPDiscoveryMessage message )
-        throws IOException
-    {
-        if ( this.localSocket == null )
-        {
-            throw new IOException( "Socket is null, cannot send message." );
-        }
-
-        if ( this.localSocket.isClosed() )
-        {
-            throw new IOException( "Socket is closed, cannot send message." );
-        }
-
-        log.debug( "sending UDPDiscoveryMessage, address [{0}], port [{1}], "
-                + "message = {2}", multicastAddress, multicastPort, message );
-
-        try
-        {
-            final byte[] bytes = serializer.serialize( message );
-
-            // put the byte array in a packet
-            final DatagramPacket packet = new DatagramPacket( bytes, bytes.length, multicastAddress, multicastPort );
-
-            log.debug( "Sending DatagramPacket. bytes.length [{0}] to {1}:{2}",
-                    bytes.length, multicastAddress, multicastPort );
-
-            localSocket.send( packet );
-        }
-        catch ( IOException e )
-        {
-            log.error( "Error sending message", e );
-            throw e;
-        }
-    }
-
-    /**
-     * Ask other to broadcast their info the the multicast address. If a lateral is non receiving it
-     * can use this. This is also called on startup so we can get info.
-     * <p>
-     * @throws IOException
-     */
-    public void requestBroadcast()
-        throws IOException
-    {
-        log.debug( "sending requestBroadcast" );
-
-        UDPDiscoveryMessage message = new UDPDiscoveryMessage();
-        message.setRequesterId( CacheInfo.listenerId );
-        message.setMessageType( BroadcastType.REQUEST );
-        send( message );
-    }
-
-    /**
-     * This sends a message broadcasting out that the host and port is available for connections.
-     * <p>
-     * It uses the vmid as the requesterDI
-     * @param host
-     * @param port
-     * @param cacheNames
-     * @throws IOException
-     */
-    public void passiveBroadcast( String host, int port, ArrayList<String> cacheNames )
-        throws IOException
-    {
-        passiveBroadcast( host, port, cacheNames, CacheInfo.listenerId );
-    }
-
-    /**
-     * This allows you to set the sender id. This is mainly for testing.
-     * <p>
-     * @param host
-     * @param port
-     * @param cacheNames names of the cache regions
-     * @param listenerId
-     * @throws IOException
-     */
-    protected void passiveBroadcast( String host, int port, ArrayList<String> cacheNames, long listenerId )
-        throws IOException
-    {
-        log.debug( "sending passiveBroadcast" );
-
-        UDPDiscoveryMessage message = new UDPDiscoveryMessage();
-        message.setHost( host );
-        message.setPort( port );
-        message.setCacheNames( cacheNames );
-        message.setRequesterId( listenerId );
-        message.setMessageType( BroadcastType.PASSIVE );
-        send( message );
-    }
-
-    /**
-     * This sends a message broadcasting our that the host and port is no longer available.
-     * <p>
-     * It uses the vmid as the requesterID
-     * <p>
-     * @param host host
-     * @param port port
-     * @param cacheNames names of the cache regions
-     * @throws IOException on error
-     */
-    public void removeBroadcast( String host, int port, ArrayList<String> cacheNames )
-        throws IOException
-    {
-        removeBroadcast( host, port, cacheNames, CacheInfo.listenerId );
-    }
-
-    /**
-     * This allows you to set the sender id. This is mainly for testing.
-     * <p>
-     * @param host host
-     * @param port port
-     * @param cacheNames names of the cache regions
-     * @param listenerId listener ID
-     * @throws IOException on error
-     */
-    protected void removeBroadcast( String host, int port, ArrayList<String> cacheNames, long listenerId )
-        throws IOException
-    {
-        log.debug( "sending removeBroadcast" );
-
-        UDPDiscoveryMessage message = new UDPDiscoveryMessage();
-        message.setHost( host );
-        message.setPort( port );
-        message.setCacheNames( cacheNames );
-        message.setRequesterId( listenerId );
-        message.setMessageType( BroadcastType.REMOVE );
-        send( message );
-    }
-}
-
-/**
- * This allows us to get the byte array from an output stream.
- * <p>
- * @author asmuts
- * @created January 15, 2002
- */
-
-class MyByteArrayOutputStream
-    extends ByteArrayOutputStream
-{
-    /**
-     * Gets the bytes attribute of the MyByteArrayOutputStream object
-     * @return The bytes value
-     */
-    public byte[] getBytes()
-    {
-        return buf;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/discovery/UDPDiscoverySenderThread.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/discovery/UDPDiscoverySenderThread.java
deleted file mode 100644
index 6c7c2a5..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/discovery/UDPDiscoverySenderThread.java
+++ /dev/null
@@ -1,147 +0,0 @@
-package org.apache.commons.jcs.utils.discovery;
-
-import java.io.IOException;
-import java.util.ArrayList;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * Used to periodically broadcast our location to other caches that might be listening.
- */
-public class UDPDiscoverySenderThread
-    implements Runnable
-{
-    /** The logger. */
-    private static final Log log = LogManager.getLog( UDPDiscoverySenderThread.class );
-
-    /**
-     * details of the host, port, and service being advertised to listen for TCP socket connections
-     */
-    private final UDPDiscoveryAttributes attributes;
-
-    /** List of known regions. */
-    private ArrayList<String> cacheNames = new ArrayList<>();
-
-    /**
-     * @param cacheNames The cacheNames to set.
-     */
-    protected void setCacheNames( ArrayList<String> cacheNames )
-    {
-        log.info( "Resetting cacheNames = [{0}]", cacheNames );
-        this.cacheNames = cacheNames;
-    }
-
-    /**
-     * @return Returns the cacheNames.
-     */
-    protected ArrayList<String> getCacheNames()
-    {
-        return cacheNames;
-    }
-
-    /**
-     * Constructs the sender with the port to tell others to connect to.
-     * <p>
-     * On construction the sender will request that the other caches let it know their addresses.
-     * @param attributes host, port, etc.
-     * @param cacheNames List of strings of the names of the region participating.
-     */
-    public UDPDiscoverySenderThread( UDPDiscoveryAttributes attributes, ArrayList<String> cacheNames )
-    {
-        this.attributes = attributes;
-
-        this.cacheNames = cacheNames;
-
-        log.debug( "Creating sender thread for discoveryAddress = [{0}] and "
-                + "discoveryPort = [{1}] myHostName = [{2}] and port = [{3}]",
-                () -> attributes.getUdpDiscoveryAddr(),
-                () -> attributes.getUdpDiscoveryPort(),
-                () -> attributes.getServiceAddress(),
-                () -> attributes.getServicePort() );
-
-        try (UDPDiscoverySender sender = new UDPDiscoverySender(
-                attributes.getUdpDiscoveryAddr(),
-                attributes.getUdpDiscoveryPort(),
-                attributes.getUdpTTL()))
-        {
-            // move this to the run method and determine how often to call it.
-            sender.requestBroadcast();
-
-            log.debug( "Sent a request broadcast to the group" );
-        }
-        catch ( IOException e )
-        {
-            log.error( "Problem sending a Request Broadcast", e );
-        }
-    }
-
-    /**
-     * Send a message.
-     */
-    @Override
-    public void run()
-    {
-        // create this connection each time.
-        // more robust
-        try (UDPDiscoverySender sender = new UDPDiscoverySender(
-                attributes.getUdpDiscoveryAddr(),
-                attributes.getUdpDiscoveryPort(),
-                attributes.getUdpTTL()))
-        {
-            sender.passiveBroadcast( attributes.getServiceAddress(), attributes.getServicePort(), cacheNames );
-
-            // todo we should consider sending a request broadcast every so
-            // often.
-
-            log.debug( "Called sender to issue a passive broadcast" );
-        }
-        catch ( IOException e )
-        {
-            log.error( "Problem calling the UDP Discovery Sender [{0}:{1}]",
-                    attributes.getUdpDiscoveryAddr(),
-                    attributes.getUdpDiscoveryPort(), e );
-        }
-    }
-
-    /**
-     * Issues a remove broadcast to the others.
-     */
-    protected void shutdown()
-    {
-        // create this connection each time.
-        // more robust
-        try (UDPDiscoverySender sender = new UDPDiscoverySender(
-                attributes.getUdpDiscoveryAddr(),
-                attributes.getUdpDiscoveryPort(),
-                attributes.getUdpTTL()))
-        {
-            sender.removeBroadcast( attributes.getServiceAddress(), attributes.getServicePort(), cacheNames );
-
-            log.debug( "Called sender to issue a remove broadcast in shudown." );
-        }
-        catch ( IOException e )
-        {
-            log.error( "Problem calling the UDP Discovery Sender", e );
-        }
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/discovery/UDPDiscoveryService.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/discovery/UDPDiscoveryService.java
deleted file mode 100644
index 1f8fc68..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/discovery/UDPDiscoveryService.java
+++ /dev/null
@@ -1,372 +0,0 @@
-package org.apache.commons.jcs.utils.discovery;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.CopyOnWriteArraySet;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.commons.jcs.engine.behavior.IRequireScheduler;
-import org.apache.commons.jcs.engine.behavior.IShutdownObserver;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-import org.apache.commons.jcs.utils.discovery.behavior.IDiscoveryListener;
-import org.apache.commons.jcs.utils.net.HostNameUtil;
-
-/**
- * This service creates a listener that can create lateral caches and add them to the no wait list.
- * <p>
- * It also creates a sender that periodically broadcasts its availability.
- * <p>
- * The sender also broadcasts a request for other caches to broadcast their addresses.
- * <p>
- * @author Aaron Smuts
- */
-public class UDPDiscoveryService
-    implements IShutdownObserver, IRequireScheduler
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog( UDPDiscoveryService.class );
-
-    /** thread that listens for messages */
-    private Thread udpReceiverThread;
-
-    /** the runnable that the receiver thread runs */
-    private UDPDiscoveryReceiver receiver;
-
-    /** the runnable that sends messages via the clock daemon */
-    private UDPDiscoverySenderThread sender = null;
-
-    /** attributes */
-    private UDPDiscoveryAttributes udpDiscoveryAttributes = null;
-
-    /** is this shut down? */
-    private boolean shutdown = false;
-
-    /** This is a set of services that have been discovered. */
-    private final Set<DiscoveredService> discoveredServices = new CopyOnWriteArraySet<>();
-
-    /** This a list of regions that are configured to use discovery. */
-    private final Set<String> cacheNames = new CopyOnWriteArraySet<>();
-
-    /** Set of listeners. */
-    private final Set<IDiscoveryListener> discoveryListeners = new CopyOnWriteArraySet<>();
-
-    /**
-     * @param attributes
-     */
-    public UDPDiscoveryService( UDPDiscoveryAttributes attributes)
-    {
-        udpDiscoveryAttributes = attributes.clone();
-
-        try
-        {
-            // todo, you should be able to set this
-            udpDiscoveryAttributes.setServiceAddress( HostNameUtil.getLocalHostAddress() );
-        }
-        catch ( UnknownHostException e )
-        {
-            log.error( "Couldn't get localhost address", e );
-        }
-
-        try
-        {
-            // todo need some kind of recovery here.
-            receiver = new UDPDiscoveryReceiver( this,
-                    getUdpDiscoveryAttributes().getUdpDiscoveryInterface(),
-                    getUdpDiscoveryAttributes().getUdpDiscoveryAddr(),
-                    getUdpDiscoveryAttributes().getUdpDiscoveryPort() );
-        }
-        catch ( IOException e )
-        {
-            log.error( "Problem creating UDPDiscoveryReceiver, address [{0}] "
-                    + "port [{1}] we won't be able to find any other caches",
-                    getUdpDiscoveryAttributes().getUdpDiscoveryAddr(),
-                    getUdpDiscoveryAttributes().getUdpDiscoveryPort(), e );
-        }
-
-        // create a sender thread
-        sender = new UDPDiscoverySenderThread( getUdpDiscoveryAttributes(), getCacheNames() );
-    }
-
-    /**
-     * @see org.apache.commons.jcs.engine.behavior.IRequireScheduler#setScheduledExecutorService(java.util.concurrent.ScheduledExecutorService)
-     */
-    @Override
-    public void setScheduledExecutorService(ScheduledExecutorService scheduledExecutor)
-    {
-        if (sender != null)
-        {
-            scheduledExecutor.scheduleAtFixedRate(sender, 0, 15, TimeUnit.SECONDS);
-        }
-
-        /** removes things that have been idle for too long */
-        UDPCleanupRunner cleanup = new UDPCleanupRunner( this );
-        // I'm going to use this as both, but it could happen
-        // that something could hang around twice the time using this as the
-        // delay and the idle time.
-        scheduledExecutor.scheduleAtFixedRate(cleanup, 0, getUdpDiscoveryAttributes().getMaxIdleTimeSec(), TimeUnit.SECONDS);
-    }
-
-    /**
-     * Send a passive broadcast in response to a request broadcast. Never send a request for a
-     * request. We can respond to our own requests, since a request broadcast is not intended as a
-     * connection request. We might want to only send messages, so we would send a request, but
-     * never a passive broadcast.
-     */
-    protected void serviceRequestBroadcast()
-    {
-        // create this connection each time.
-        // more robust
-        try (UDPDiscoverySender sender = new UDPDiscoverySender(
-                getUdpDiscoveryAttributes().getUdpDiscoveryAddr(),
-                getUdpDiscoveryAttributes().getUdpDiscoveryPort(),
-                getUdpDiscoveryAttributes().getUdpTTL()))
-        {
-            sender.passiveBroadcast( getUdpDiscoveryAttributes().getServiceAddress(), getUdpDiscoveryAttributes()
-                .getServicePort(), this.getCacheNames() );
-
-            // todo we should consider sending a request broadcast every so
-            // often.
-
-            log.debug( "Called sender to issue a passive broadcast" );
-        }
-        catch ( IOException e )
-        {
-            log.error( "Problem calling the UDP Discovery Sender, address [{0}] "
-                    + "port [{1}]",
-                    getUdpDiscoveryAttributes().getUdpDiscoveryAddr(),
-                    getUdpDiscoveryAttributes().getUdpDiscoveryPort(), e );
-        }
-    }
-
-    /**
-     * Adds a region to the list that is participating in discovery.
-     * <p>
-     * @param cacheName
-     */
-    public void addParticipatingCacheName( String cacheName )
-    {
-        cacheNames.add( cacheName );
-        sender.setCacheNames( getCacheNames() );
-    }
-
-    /**
-     * Removes the discovered service from the list and calls the discovery listener.
-     * <p>
-     * @param service
-     */
-    public void removeDiscoveredService( DiscoveredService service )
-    {
-        boolean contained = getDiscoveredServices().remove( service );
-
-        if ( contained )
-        {
-            log.info( "Removing {0}", service );
-        }
-
-        for (IDiscoveryListener listener : getDiscoveryListeners())
-        {
-            listener.removeDiscoveredService( service );
-        }
-    }
-
-    /**
-     * Add a service to the list. Update the held copy if we already know about it.
-     * <p>
-     * @param discoveredService discovered service
-     */
-    protected void addOrUpdateService( DiscoveredService discoveredService )
-    {
-        Set<DiscoveredService> discoveredServices = getDiscoveredServices();
-        // Since this is a set we can add it over an over.
-        // We want to replace the old one, since we may add info that is not part of the equals.
-        // The equals method on the object being added is intentionally restricted.
-        if ( !discoveredServices.contains( discoveredService ) )
-        {
-            log.info( "Set does not contain service. I discovered {0}", discoveredService );
-            log.debug( "Adding service in the set {0}", discoveredService );
-            discoveredServices.add( discoveredService );
-        }
-        else
-        {
-            log.debug( "Set contains service." );
-            log.debug( "Updating service in the set {0}", discoveredService );
-
-            // Update the list of cache names if it has changed.
-            DiscoveredService theOldServiceInformation = null;
-            // need to update the time this sucks. add has no effect convert to a map
-            for (DiscoveredService service1 : discoveredServices)
-            {
-                if ( discoveredService.equals( service1 ) )
-                {
-                    theOldServiceInformation = service1;
-                    break;
-                }
-            }
-            if ( theOldServiceInformation != null )
-            {
-                if ( !theOldServiceInformation.getCacheNames().equals(
-                        discoveredService.getCacheNames() ) )
-                {
-                    log.info( "List of cache names changed for service: {0}",
-                            discoveredService );
-                }
-            }
-
-            // replace it, we want to reset the payload and the last heard from time.
-            discoveredServices.remove( discoveredService );
-            discoveredServices.add( discoveredService );
-        }
-
-        // Always Notify the listeners
-        // If we don't do this, then if a region using the default config is initialized after notification,
-        // it will never get the service in it's no wait list.
-        // Leave it to the listeners to decide what to do.
-        for (IDiscoveryListener listener : getDiscoveryListeners())
-        {
-            listener.addDiscoveredService( discoveredService );
-        }
-    }
-
-    /**
-     * Get all the cache names we have facades for.
-     * <p>
-     * @return ArrayList
-     */
-    protected ArrayList<String> getCacheNames()
-    {
-        ArrayList<String> names = new ArrayList<>();
-        names.addAll( cacheNames );
-        return names;
-    }
-
-    /**
-     * @param attr The UDPDiscoveryAttributes to set.
-     */
-    public void setUdpDiscoveryAttributes( UDPDiscoveryAttributes attr )
-    {
-        this.udpDiscoveryAttributes = attr;
-    }
-
-    /**
-     * @return Returns the lca.
-     */
-    public UDPDiscoveryAttributes getUdpDiscoveryAttributes()
-    {
-        return this.udpDiscoveryAttributes;
-    }
-
-    /**
-     * Start necessary receiver thread
-     */
-    public void startup()
-    {
-        udpReceiverThread = new Thread(receiver);
-        udpReceiverThread.setDaemon(true);
-        // udpReceiverThread.setName( t.getName() + "--UDPReceiver" );
-        udpReceiverThread.start();
-    }
-
-    /**
-     * Shuts down the receiver.
-     */
-    @Override
-    public void shutdown()
-    {
-        if ( !shutdown )
-        {
-            shutdown = true;
-
-            // no good way to do this right now.
-            if (receiver != null)
-            {
-                log.info( "Shutting down UDP discovery service receiver." );
-                receiver.shutdown();
-                udpReceiverThread.interrupt();
-            }
-
-            if (sender != null)
-            {
-                log.info( "Shutting down UDP discovery service sender." );
-                // also call the shutdown on the sender thread itself, which
-                // will result in a remove command.
-                sender.shutdown();
-            }
-        }
-        else
-        {
-            log.debug( "Shutdown already called." );
-        }
-    }
-
-    /**
-     * @return Returns the discoveredServices.
-     */
-    public Set<DiscoveredService> getDiscoveredServices()
-    {
-        return discoveredServices;
-    }
-
-    /**
-     * @return the discoveryListeners
-     */
-    private Set<IDiscoveryListener> getDiscoveryListeners()
-    {
-        return discoveryListeners;
-    }
-
-    /**
-     * @return the discoveryListeners
-     */
-    public Set<IDiscoveryListener> getCopyOfDiscoveryListeners()
-    {
-        Set<IDiscoveryListener> copy = new HashSet<>();
-        copy.addAll( getDiscoveryListeners() );
-        return copy;
-    }
-
-    /**
-     * Adds a listener.
-     * <p>
-     * @param listener
-     * @return true if it wasn't already in the set
-     */
-    public boolean addDiscoveryListener( IDiscoveryListener listener )
-    {
-        return getDiscoveryListeners().add( listener );
-    }
-
-    /**
-     * Removes a listener.
-     * <p>
-     * @param listener
-     * @return true if it was in the set
-     */
-    public boolean removeDiscoveryListener( IDiscoveryListener listener )
-    {
-        return getDiscoveryListeners().remove( listener );
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/discovery/behavior/IDiscoveryListener.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/discovery/behavior/IDiscoveryListener.java
deleted file mode 100644
index 8a0d9d9..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/discovery/behavior/IDiscoveryListener.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package org.apache.commons.jcs.utils.discovery.behavior;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.utils.discovery.DiscoveredService;
-
-/**
- * Interface for things that want to listen to discovery events. This will allow discovery to be
- * used outside of the TCP lateral.
- */
-public interface IDiscoveryListener
-{
-    /**
-     * Add the service if needed. This does not necessarily mean that the service is not already
-     * added. This can be called if there is a change in service information, such as the cacheNames.
-     * <p>
-     * @param service the service to add
-     */
-    void addDiscoveredService( DiscoveredService service );
-
-    /**
-     * Remove the service from the list.
-     * <p>
-     * @param service the service to remove
-     */
-    void removeDiscoveredService( DiscoveredService service );
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/net/HostNameUtil.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/net/HostNameUtil.java
deleted file mode 100644
index 2c3bce6..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/net/HostNameUtil.java
+++ /dev/null
@@ -1,195 +0,0 @@
-package org.apache.commons.jcs.utils.net;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.net.InetAddress;
-import java.net.NetworkInterface;
-import java.net.SocketException;
-import java.net.UnknownHostException;
-import java.util.Enumeration;
-/*
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * Simple utility for getting the local host name.
- * <p>
- * @author Aaron Smuts
- */
-public class HostNameUtil
-{
-    /** The logger. */
-    private static final Log log = LogManager.getLog( HostNameUtil.class );
-
-    /**
-     * Gets the address for the local machine.
-     * <p>
-     * @return InetAddress.getLocalHost().getHostAddress()
-     * @throws UnknownHostException
-     */
-    public static String getLocalHostAddress() throws UnknownHostException
-    {
-        try
-        {
-            String hostAddress = getLocalHostLANAddress().getHostAddress();
-            log.debug( "hostAddress = [{0}]", hostAddress );
-            return hostAddress;
-        }
-        catch ( UnknownHostException e1 )
-        {
-            log.error( "Couldn't get localhost address", e1 );
-            throw e1;
-        }
-    }
-
-    /**
-     * Returns an <code>InetAddress</code> object encapsulating what is most likely the machine's
-     * LAN IP address.
-     * <p>
-     * This method is intended for use as a replacement of JDK method
-     * <code>InetAddress.getLocalHost</code>, because that method is ambiguous on Linux systems.
-     * Linux systems enumerate the loopback network interface the same way as regular LAN network
-     * interfaces, but the JDK <code>InetAddress.getLocalHost</code> method does not specify the
-     * algorithm used to select the address returned under such circumstances, and will often return
-     * the loopback address, which is not valid for network communication. Details <a
-     * href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4665037">here</a>.
-     * <p>
-     * This method will scan all IP addresses on all network interfaces on the host machine to
-     * determine the IP address most likely to be the machine's LAN address. If the machine has
-     * multiple IP addresses, this method will prefer a site-local IP address (e.g. 192.168.x.x or
-     * 10.10.x.x, usually IPv4) if the machine has one (and will return the first site-local address
-     * if the machine has more than one), but if the machine does not hold a site-local address,
-     * this method will return simply the first non-loopback address found (IPv4 or IPv6).</p>
-     * <p>
-     * If this method cannot find a non-loopback address using this selection algorithm, it will
-     * fall back to calling and returning the result of JDK method
-     * <code>InetAddress.getLocalHost</code>.
-     * <p>
-     * <a href="http://issues.apache.org/jira/browse/JCS-40">JIR ISSUE JCS-40</a>
-     * <p>
-     * @return InetAddress
-     * @throws UnknownHostException If the LAN address of the machine cannot be found.
-     */
-    public static InetAddress getLocalHostLANAddress()
-        throws UnknownHostException
-    {
-        try
-        {
-            InetAddress candidateAddress = null;
-            // Iterate all NICs (network interface cards)...
-            for ( Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces(); ifaces.hasMoreElements(); )
-            {
-                NetworkInterface iface = ifaces.nextElement();
-                // Iterate all IP addresses assigned to each card...
-                for ( Enumeration<InetAddress> inetAddrs = iface.getInetAddresses(); inetAddrs.hasMoreElements(); )
-                {
-                    InetAddress inetAddr = inetAddrs.nextElement();
-                    if ( !inetAddr.isLoopbackAddress() )
-                    {
-                        if ( inetAddr.isSiteLocalAddress() )
-                        {
-                            // Found non-loopback site-local address. Return it immediately...
-                            return inetAddr;
-                        }
-                        else if ( candidateAddress == null )
-                        {
-                            // Found non-loopback address, but not necessarily site-local.
-                            // Store it as a candidate to be returned if site-local address is not subsequently found...
-                            candidateAddress = inetAddr;
-                            // Note that we don't repeatedly assign non-loopback non-site-local addresses as candidates,
-                            // only the first. For subsequent iterations, candidate will be non-null.
-                        }
-                    }
-                }
-            }
-            if ( candidateAddress != null )
-            {
-                // We did not find a site-local address, but we found some other non-loopback address.
-                // Server might have a non-site-local address assigned to its NIC (or it might be running
-                // IPv6 which deprecates the "site-local" concept).
-                // Return this non-loopback candidate address...
-                return candidateAddress;
-            }
-            // At this point, we did not find a non-loopback address.
-            // Fall back to returning whatever InetAddress.getLocalHost() returns...
-            InetAddress jdkSuppliedAddress = InetAddress.getLocalHost();
-            if ( jdkSuppliedAddress == null )
-            {
-                throw new UnknownHostException( "The JDK InetAddress.getLocalHost() method unexpectedly returned null." );
-            }
-            return jdkSuppliedAddress;
-        }
-        catch ( SocketException e )
-        {
-            UnknownHostException unknownHostException = new UnknownHostException( "Failed to determine LAN address: "
-                + e );
-            unknownHostException.initCause( e );
-            throw unknownHostException;
-        }
-    }
-
-    /**
-     * On systems with multiple network interfaces and mixed IPv6/IPv4 get a valid network
-     * interface for binding to multicast
-     *
-     * @return a network interface suitable for multicast
-     * @throws SocketException if a problem occurs while reading the network interfaces
-     */
-    public static NetworkInterface getMulticastNetworkInterface() throws SocketException
-    {
-        Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
-        while (networkInterfaces.hasMoreElements())
-        {
-            NetworkInterface networkInterface = networkInterfaces.nextElement();
-            Enumeration<InetAddress> addressesFromNetworkInterface = networkInterface.getInetAddresses();
-            while (addressesFromNetworkInterface.hasMoreElements())
-            {
-                InetAddress inetAddress = addressesFromNetworkInterface.nextElement();
-                if (inetAddress.isSiteLocalAddress()
-                        && !inetAddress.isAnyLocalAddress()
-                        && !inetAddress.isLinkLocalAddress()
-                        && !inetAddress.isLoopbackAddress()
-                        && !inetAddress.isMulticastAddress())
-                {
-                    return networkInterface;
-                }
-            }
-        }
-
-        return null;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/serialization/CompressingSerializer.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/serialization/CompressingSerializer.java
deleted file mode 100644
index bbc0ee9..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/serialization/CompressingSerializer.java
+++ /dev/null
@@ -1,69 +0,0 @@
-package org.apache.commons.jcs.utils.serialization;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-
-import org.apache.commons.jcs.utils.zip.CompressionUtil;
-
-/**
- * Performs default serialization and de-serialization. It gzips the value.
- */
-public class CompressingSerializer extends StandardSerializer
-{
-    /**
-     * Serializes an object using default serialization. Compresses the byte array.
-     * <p>
-     * @param obj object
-     * @return byte[]
-     * @throws IOException on i/o problem
-     */
-    @Override
-    public <T> byte[] serialize( T obj )
-        throws IOException
-    {
-        byte[] uncompressed = super.serialize(obj);
-        byte[] compressed = CompressionUtil.compressByteArray( uncompressed );
-        return compressed;
-    }
-
-    /**
-     * Uses default de-serialization to turn a byte array into an object. Decompresses the value
-     * first. All exceptions are converted into IOExceptions.
-     * <p>
-     * @param data data bytes
-     * @param loader class loader to use
-     * @return Object
-     * @throws IOException on i/o problem
-     * @throws ClassNotFoundException if class is not found during deserialization
-     */
-    @Override
-    public <T> T deSerialize( byte[] data, ClassLoader loader )
-        throws IOException, ClassNotFoundException
-    {
-        if ( data == null )
-        {
-            return null;
-        }
-        
-        byte[] decompressedByteArray = CompressionUtil.decompressByteArray( data );
-        return super.deSerialize(decompressedByteArray, loader);
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/serialization/SerializationConversionUtil.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/serialization/SerializationConversionUtil.java
deleted file mode 100644
index e179044..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/serialization/SerializationConversionUtil.java
+++ /dev/null
@@ -1,149 +0,0 @@
-package org.apache.commons.jcs.utils.serialization;
-
-import java.io.IOException;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.CacheElementSerialized;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheElementSerialized;
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * This uses a supplied Serializer to convert to and from cache elements.
- * <p>
- * @author Aaron Smuts
- */
-public class SerializationConversionUtil
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog( SerializationConversionUtil.class );
-
-    /**
-     * This returns a wrapper that has a serialized version of the value instead
-     * of the value.
-     * <p>
-     * @param element
-     * @param elementSerializer
-     *            the serializer to be used.
-     * @return null for null;
-     * @throws IOException
-     */
-    public static <K, V> ICacheElementSerialized<K, V> getSerializedCacheElement( ICacheElement<K, V> element,
-                                                                    IElementSerializer elementSerializer )
-        throws IOException
-    {
-        if ( element == null )
-        {
-            return null;
-        }
-
-        byte[] serializedValue = null;
-
-        // if it has already been serialized, don't do it again.
-        if ( element instanceof ICacheElementSerialized )
-        {
-            serializedValue = ( (ICacheElementSerialized<K, V>) element ).getSerializedValue();
-        }
-        else
-        {
-            if ( elementSerializer != null )
-            {
-                try
-                {
-                    serializedValue = elementSerializer.serialize(element.getVal());
-
-                    // update size in bytes
-                    element.getElementAttributes().setSize(serializedValue.length);
-                }
-                catch ( IOException e )
-                {
-                    log.error( "Problem serializing object.", e );
-                    throw e;
-                }
-            }
-            else
-            {
-                // we could just use the default.
-                throw new IOException( "Could not serialize object. The ElementSerializer is null." );
-            }
-        }
-        ICacheElementSerialized<K, V> serialized = new CacheElementSerialized<>(
-                element.getCacheName(), element.getKey(), serializedValue, element.getElementAttributes() );
-
-        return serialized;
-    }
-
-    /**
-     * This returns a wrapper that has a de-serialized version of the value
-     * instead of the serialized value.
-     * <p>
-     * @param serialized
-     * @param elementSerializer
-     *            the serializer to be used.
-     * @return null for null;
-     * @throws IOException
-     * @throws ClassNotFoundException
-     */
-    public static <K, V> ICacheElement<K, V> getDeSerializedCacheElement( ICacheElementSerialized<K, V> serialized,
-                                                            IElementSerializer elementSerializer )
-        throws IOException, ClassNotFoundException
-    {
-        if ( serialized == null )
-        {
-            return null;
-        }
-
-        V deSerializedValue = null;
-
-        if ( elementSerializer != null )
-        {
-            try
-            {
-                try
-                {
-                    deSerializedValue = elementSerializer.deSerialize( serialized.getSerializedValue(), null );
-                }
-                catch ( ClassNotFoundException e )
-                {
-                    log.error( "Problem de-serializing object.", e );
-                    throw e;
-                }
-            }
-            catch ( IOException e )
-            {
-                log.error( "Problem de-serializing object.", e );
-                throw e;
-            }
-        }
-        else
-        {
-            // we could just use the default.
-            throw new IOException( "Could not de-serialize object. The ElementSerializer is null." );
-       }
-        ICacheElement<K, V> deSerialized = new CacheElement<>( serialized.getCacheName(), serialized.getKey(), deSerializedValue );
-        deSerialized.setElementAttributes( serialized.getElementAttributes() );
-
-        return deSerialized;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/serialization/StandardSerializer.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/serialization/StandardSerializer.java
deleted file mode 100644
index 0915800..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/serialization/StandardSerializer.java
+++ /dev/null
@@ -1,82 +0,0 @@
-package org.apache.commons.jcs.utils.serialization;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.io.ObjectInputStreamClassLoaderAware;
-
-/**
- * Performs default serialization and de-serialization.
- * <p>
- * @author Aaron Smuts
- */
-public class StandardSerializer
-    implements IElementSerializer
-{
-    /**
-     * Serializes an object using default serialization.
-     * <p>
-     * @param obj
-     * @return byte[]
-     * @throws IOException
-     */
-    @Override
-    public <T> byte[] serialize(T obj)
-        throws IOException
-    {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-
-        try (ObjectOutputStream oos = new ObjectOutputStream(baos))
-        {
-            oos.writeObject(obj);
-        }
-
-        return baos.toByteArray();
-    }
-
-    /**
-     * Uses default de-serialization to turn a byte array into an object. All exceptions are
-     * converted into IOExceptions.
-     * <p>
-     * @param data data bytes
-     * @param loader class loader to use
-     * @return Object
-     * @throws IOException
-     * @throws ClassNotFoundException
-     */
-    @Override
-    public <T> T deSerialize(byte[] data, ClassLoader loader)
-        throws IOException, ClassNotFoundException
-    {
-        try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
-             ObjectInputStream ois = new ObjectInputStreamClassLoaderAware(bais, loader))
-        {
-            @SuppressWarnings("unchecked") // Need to cast from Object
-            T readObject = (T) ois.readObject();
-            return readObject;
-        }
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/servlet/JCSServletContextListener.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/servlet/JCSServletContextListener.java
deleted file mode 100644
index 03d01b1..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/servlet/JCSServletContextListener.java
+++ /dev/null
@@ -1,73 +0,0 @@
-package org.apache.commons.jcs.utils.servlet;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import javax.servlet.ServletContextEvent;
-import javax.servlet.ServletContextListener;
-
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * If you add this to the context listeners section of your web.xml file, this will shutdown JCS
- * gracefully.
- * <p>
- * Add the following to the top of your web.xml file.
- *
- * <pre>
- *  &lt;listener&gt;
- *  &lt;listener-class&gt;
- *  org.apache.commons.jcs.utils.servlet.JCSServletContextListener
- *  &lt;/listener-class&gt;
- *  &lt;/listener&gt;
- * </pre>
- * @author Aaron Smuts
- */
-public class JCSServletContextListener
-    implements ServletContextListener
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog( JCSServletContextListener.class );
-
-    /**
-     * This does nothing. We don't want to initialize the cache here.
-     * <p>
-     * @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)
-     */
-    @Override
-    public void contextInitialized( ServletContextEvent arg0 )
-    {
-        log.debug( "contextInitialized" );
-    }
-
-    /**
-     * Shutdown JCS.
-     * <p>
-     * @see javax.servlet.ServletContextListener#contextDestroyed(javax.servlet.ServletContextEvent)
-     */
-    @Override
-    public void contextDestroyed( ServletContextEvent arg0 )
-    {
-        log.debug( "contextDestroyed, shutting down JCS." );
-
-        JCS.shutdown();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/struct/AbstractLRUMap.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/struct/AbstractLRUMap.java
deleted file mode 100644
index bdb12e3..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/struct/AbstractLRUMap.java
+++ /dev/null
@@ -1,542 +0,0 @@
-package org.apache.commons.jcs.utils.struct;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.AbstractMap;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.stream.Collectors;
-
-import org.apache.commons.jcs.engine.control.group.GroupAttrName;
-import org.apache.commons.jcs.engine.stats.StatElement;
-import org.apache.commons.jcs.engine.stats.Stats;
-import org.apache.commons.jcs.engine.stats.behavior.IStatElement;
-import org.apache.commons.jcs.engine.stats.behavior.IStats;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * This is a simple LRUMap. It implements most of the map methods. It is not recommended that you
- * use any but put, get, remove, and clear.
- * <p>
- * Children can implement the processRemovedLRU method if they want to handle the removal of the
- * least recently used item.
- * <p>
- * This class was abstracted out of the LRU Memory cache. Put, remove, and get should be thread
- * safe. It uses a hashtable and our own double linked list.
- * <p>
- * Locking is done on the instance.
- * <p>
- * @author aaron smuts
- */
-public abstract class AbstractLRUMap<K, V>
-    implements Map<K, V>
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog( AbstractLRUMap.class );
-
-    /** double linked list for lru */
-    private final DoubleLinkedList<LRUElementDescriptor<K, V>> list;
-
-    /** Map where items are stored by key. */
-    private final Map<K, LRUElementDescriptor<K, V>> map;
-
-    /** lock to keep map and list synchronous */
-    private final Lock lock = new ReentrantLock();
-
-    /** stats */
-    private long hitCnt = 0;
-
-    /** stats */
-    private long missCnt = 0;
-
-    /** stats */
-    private long putCnt = 0;
-
-    /**
-     * This creates an unbounded version. Setting the max objects will result in spooling on
-     * subsequent puts.
-     */
-    public AbstractLRUMap()
-    {
-        list = new DoubleLinkedList<>();
-
-        // normal hashtable is faster for
-        // sequential keys.
-        map = new ConcurrentHashMap<>();
-    }
-
-
-    /**
-     * This simply returns the number of elements in the map.
-     * <p>
-     * @see java.util.Map#size()
-     */
-    @Override
-    public int size()
-    {
-        return map.size();
-    }
-
-    /**
-     * This removes all the items. It clears the map and the double linked list.
-     * <p>
-     * @see java.util.Map#clear()
-     */
-    @Override
-    public void clear()
-    {
-        lock.lock();
-        try
-        {
-            map.clear();
-            list.removeAll();
-        }
-        finally
-        {
-            lock.unlock();
-        }
-    }
-
-    /**
-     * Returns true if the map is empty.
-     * <p>
-     * @see java.util.Map#isEmpty()
-     */
-    @Override
-    public boolean isEmpty()
-    {
-        return map.isEmpty();
-    }
-
-    /**
-     * Returns true if the map contains an element for the supplied key.
-     * <p>
-     * @see java.util.Map#containsKey(java.lang.Object)
-     */
-    @Override
-    public boolean containsKey( Object key )
-    {
-        return map.containsKey( key );
-    }
-
-    /**
-     * This is an expensive operation that determines if the object supplied is mapped to any key.
-     * <p>
-     * @see java.util.Map#containsValue(java.lang.Object)
-     */
-    @Override
-    public boolean containsValue( Object value )
-    {
-        return map.containsValue( value );
-    }
-
-    /**
-     * @return map.values();
-     */
-    @Override
-    public Collection<V> values()
-    {
-        return map.values().stream()
-                .map(value -> value.getPayload())
-                .collect(Collectors.toList());
-    }
-
-    /**
-     * @param source
-     */
-    @Override
-    public void putAll( Map<? extends K, ? extends V> source )
-    {
-        if ( source != null )
-        {
-            source.entrySet()
-                .forEach(entry -> put(entry.getKey(), entry.getValue()));
-        }
-    }
-
-    /**
-     * @param key
-     * @return Object
-     */
-    @Override
-    public V get( Object key )
-    {
-        V retVal;
-
-        log.debug( "getting item  for key {0}", key );
-
-        LRUElementDescriptor<K, V> me = map.get( key );
-
-        if ( me == null )
-        {
-            missCnt++;
-            retVal = null;
-        }
-        else
-        {
-            hitCnt++;
-            retVal = me.getPayload();
-            list.makeFirst( me );
-        }
-
-        if ( me == null )
-        {
-            log.debug( "LRUMap miss for {0}", key );
-        }
-        else
-        {
-            log.debug( "LRUMap hit for {0}", key );
-        }
-
-        // verifyCache();
-        return retVal;
-    }
-
-    /**
-     * This gets an element out of the map without adjusting it's position in the LRU. In other
-     * words, this does not count as being used. If the element is the last item in the list, it
-     * will still be the last time in the list.
-     * <p>
-     * @param key
-     * @return Object
-     */
-    public V getQuiet( Object key )
-    {
-        V ce = null;
-        LRUElementDescriptor<K, V> me = map.get( key );
-
-        if ( me != null )
-        {
-            ce = me.getPayload();
-        }
-
-        if ( me == null )
-        {
-            log.debug( "LRUMap quiet miss for {0}", key );
-        }
-        else
-        {
-            log.debug( "LRUMap quiet hit for {0}", key );
-        }
-
-        return ce;
-    }
-
-    /**
-     * @param key
-     * @return Object removed
-     */
-    @Override
-    public V remove( Object key )
-    {
-        log.debug( "removing item for key: {0}", key );
-
-        // remove single item.
-        lock.lock();
-        try
-        {
-            LRUElementDescriptor<K, V> me = map.remove(key);
-
-            if (me != null)
-            {
-                list.remove(me);
-                return me.getPayload();
-            }
-        }
-        finally
-        {
-            lock.unlock();
-        }
-
-        return null;
-    }
-
-    /**
-     * @param key
-     * @param value
-     * @return Object
-     */
-    @Override
-    public V put(K key, V value)
-    {
-        putCnt++;
-
-        LRUElementDescriptor<K, V> old = null;
-        LRUElementDescriptor<K, V> me = new LRUElementDescriptor<>(key, value);
-
-        lock.lock();
-        try
-        {
-            list.addFirst( me );
-            old = map.put(key, me);
-
-            // If the node was the same as an existing node, remove it.
-            if ( old != null && key.equals(old.getKey()))
-            {
-                list.remove( old );
-            }
-        }
-        finally
-        {
-            lock.unlock();
-        }
-
-        // If the element limit is reached, we need to spool
-        if (shouldRemove())
-        {
-            log.debug( "In memory limit reached, removing least recently used." );
-
-            // The spool will put them in a disk event queue, so there is no
-            // need to pre-queue the queuing. This would be a bit wasteful
-            // and wouldn't save much time in this synchronous call.
-            while (shouldRemove())
-            {
-                lock.lock();
-                try
-                {
-                    LRUElementDescriptor<K, V> last = list.getLast();
-                    if (last != null)
-                    {
-                        processRemovedLRU(last.getKey(), last.getPayload());
-                        if (map.remove(last.getKey()) == null)
-                        {
-                            log.warn("update: remove failed for key: {0}",
-                                    () -> last.getKey());
-                            verifyCache();
-                        }
-                        list.removeLast();
-                    }
-                    else
-                    {
-                        verifyCache();
-                        throw new Error("update: last is null!");
-                    }
-                }
-                finally
-                {
-                    lock.unlock();
-                }
-            }
-
-            log.debug( "update: After spool map size: {0}", () -> map.size() );
-            if ( map.size() != list.size() )
-            {
-                log.error("update: After spool, size mismatch: map.size() = {0}, "
-                        + "linked list size = {1}",
-                        () -> map.size(), () -> list.size());
-            }
-        }
-
-        if ( old != null )
-        {
-            return old.getPayload();
-        }
-        return null;
-    }
-
-    protected abstract boolean shouldRemove();
-
-    /**
-     * Dump the cache entries from first to list for debugging.
-     */
-    @SuppressWarnings("unchecked") // No generics for public fields
-    public void dumpCacheEntries()
-    {
-        if (log.isTraceEnabled())
-        {
-            log.trace("dumpingCacheEntries");
-            for (LRUElementDescriptor<K, V> me = list.getFirst(); me != null; me = (LRUElementDescriptor<K, V>) me.next)
-            {
-                log.trace("dumpCacheEntries> key={0}, val={1}", me.getKey(), me.getPayload());
-            }
-        }
-    }
-
-    /**
-     * Dump the cache map for debugging.
-     */
-    public void dumpMap()
-    {
-        if (log.isTraceEnabled())
-        {
-            log.trace("dumpingMap");
-            map.entrySet().forEach(e ->
-                log.trace("dumpMap> key={0}, val={1}", e.getKey(), e.getValue().getPayload()));
-        }
-    }
-
-    /**
-     * Checks to see if all the items that should be in the cache are. Checks consistency between
-     * List and map.
-     */
-    @SuppressWarnings("unchecked") // No generics for public fields
-    protected void verifyCache()
-    {
-        if ( !log.isTraceEnabled() )
-        {
-            return;
-        }
-
-        log.trace( "verifycache: mapContains {0} elements, linked list "
-                + "contains {1} elements", map.size(), list.size() );
-        log.trace( "verifycache: checking linked list by key" );
-        for (LRUElementDescriptor<K, V> li = list.getFirst(); li != null; li = (LRUElementDescriptor<K, V>) li.next )
-        {
-            K key = li.getKey();
-            if ( !map.containsKey( key ) )
-            {
-                log.error( "verifycache: map does not contain key : {0}", li.getKey() );
-                log.error( "li.hashcode={0}", li.getKey().hashCode() );
-                log.error( "key class={0}", key.getClass() );
-                log.error( "key hashcode={0}", key.hashCode() );
-                log.error( "key toString={0}", key.toString() );
-                if ( key instanceof GroupAttrName )
-                {
-                    GroupAttrName<?> name = (GroupAttrName<?>) key;
-                    log.error( "GroupID hashcode={0}", name.groupId.hashCode() );
-                    log.error( "GroupID.class={0}", name.groupId.getClass() );
-                    log.error( "AttrName hashcode={0}", name.attrName.hashCode() );
-                    log.error( "AttrName.class={0}", name.attrName.getClass() );
-                }
-                dumpMap();
-            }
-            else if ( map.get( li.getKey() ) == null )
-            {
-                log.error( "verifycache: linked list retrieval returned null for key: {0}",
-                        li.getKey() );
-            }
-        }
-
-        log.trace( "verifycache: checking linked list by value " );
-        for (LRUElementDescriptor<K, V> li3 = list.getFirst(); li3 != null; li3 = (LRUElementDescriptor<K, V>) li3.next )
-        {
-            if ( map.containsValue( li3 ) == false )
-            {
-                log.error( "verifycache: map does not contain value : {0}", li3 );
-                dumpMap();
-            }
-        }
-
-        log.trace( "verifycache: checking via keysets!" );
-        map.forEach((key, value) -> {
-            boolean found = false;
-
-            for (LRUElementDescriptor<K, V> li2 = list.getFirst(); li2 != null; li2 = (LRUElementDescriptor<K, V>) li2.next )
-            {
-                if ( key.equals( li2.getKey() ) )
-                {
-                    found = true;
-                    break;
-                }
-            }
-            if ( !found )
-            {
-                log.error( "verifycache: key not found in list : {0}", key );
-                dumpCacheEntries();
-                if ( map.containsKey( key ) )
-                {
-                    log.error( "verifycache: map contains key" );
-                }
-                else
-                {
-                    log.error( "verifycache: map does NOT contain key, what the HECK!" );
-                }
-            }
-        });
-    }
-
-    /**
-     * This is called when an item is removed from the LRU. We just log some information.
-     * <p>
-     * Children can implement this method for special behavior.
-     * @param key
-     * @param value
-     */
-    protected void processRemovedLRU(K key, V value )
-    {
-        log.debug( "Removing key: [{0}] from LRUMap store, value = [{1}]", key, value );
-        log.debug( "LRUMap store size: \"{0}\".", this.size() );
-    }
-
-    /**
-     * @return IStats
-     */
-    public IStats getStatistics()
-    {
-        IStats stats = new Stats();
-        stats.setTypeName( "LRUMap" );
-
-        ArrayList<IStatElement<?>> elems = new ArrayList<>();
-
-        elems.add(new StatElement<>( "List Size", Integer.valueOf(list.size()) ) );
-        elems.add(new StatElement<>( "Map Size", Integer.valueOf(map.size()) ) );
-        elems.add(new StatElement<>( "Put Count", Long.valueOf(putCnt) ) );
-        elems.add(new StatElement<>( "Hit Count", Long.valueOf(hitCnt) ) );
-        elems.add(new StatElement<>( "Miss Count", Long.valueOf(missCnt) ) );
-
-        stats.setStatElements( elems );
-
-        return stats;
-    }
-
-    /**
-     * This returns a set of entries. Our LRUMapEntry is used since the value stored in the
-     * underlying map is a node in the double linked list. We wouldn't want to return this to the
-     * client, so we construct a new entry with the payload of the node.
-     * <p>
-     * TODO we should return out own set wrapper, so we can avoid the extra object creation if it
-     * isn't necessary.
-     * <p>
-     * @see java.util.Map#entrySet()
-     */
-    @Override
-    public Set<Map.Entry<K, V>> entrySet()
-    {
-        lock.lock();
-        try
-        {
-            return map.entrySet().stream()
-                    .map(entry -> new AbstractMap.SimpleEntry<K, V>(
-                            entry.getKey(), entry.getValue().getPayload()))
-                    .collect(Collectors.toSet());
-        }
-        finally
-        {
-            lock.unlock();
-        }
-    }
-
-    /**
-     * @return map.keySet();
-     */
-    @Override
-    public Set<K> keySet()
-    {
-        return map.values().stream()
-                .map(value -> value.getKey())
-                .collect(Collectors.toSet());
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/struct/DoubleLinkedList.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/struct/DoubleLinkedList.java
deleted file mode 100644
index 0736c32..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/struct/DoubleLinkedList.java
+++ /dev/null
@@ -1,291 +0,0 @@
-package org.apache.commons.jcs.utils.struct;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * This is a generic thread safe double linked list. It's very simple and all the operations are so
- * quick that course grained synchronization is more than acceptable.
- */
-@SuppressWarnings({ "unchecked", "rawtypes" }) // Don't know how to resolve this with generics
-public class DoubleLinkedList<T extends DoubleLinkedListNode>
-{
-    /** record size to avoid having to iterate */
-    private int size = 0;
-
-    /** The logger */
-    private static final Log log = LogManager.getLog( DoubleLinkedList.class );
-
-    /** LRU double linked list head node */
-    private T first;
-
-    /** LRU double linked list tail node */
-    private T last;
-
-    /**
-     * Default constructor.
-     */
-    public DoubleLinkedList()
-    {
-        super();
-    }
-
-    /**
-     * Adds a new node to the end of the link list.
-     * <p>
-     * @param me The feature to be added to the Last
-     */
-    public synchronized void addLast(T me)
-    {
-        if ( first == null )
-        {
-            // empty list.
-            first = me;
-        }
-        else
-        {
-            last.next = me;
-            me.prev = last;
-        }
-        last = me;
-        size++;
-    }
-
-    /**
-     * Adds a new node to the start of the link list.
-     * <p>
-     * @param me The feature to be added to the First
-     */
-    public synchronized void addFirst(T me)
-    {
-        if ( last == null )
-        {
-            // empty list.
-            last = me;
-        }
-        else
-        {
-            first.prev = me;
-            me.next = first;
-        }
-        first = me;
-        size++;
-    }
-
-    /**
-     * Returns the last node from the link list, if there are any nodes.
-     * <p>
-     * @return The last node.
-     */
-    public synchronized T getLast()
-    {
-        log.debug( "returning last node" );
-        return last;
-    }
-
-    /**
-     * Removes the specified node from the link list.
-     * <p>
-     * @return DoubleLinkedListNode, the first node.
-     */
-    public synchronized T getFirst()
-    {
-        log.debug( "returning first node" );
-        return first;
-    }
-
-    /**
-     * Moves an existing node to the start of the link list.
-     * <p>
-     * @param ln The node to set as the head.
-     */
-    public synchronized void makeFirst(T ln)
-    {
-        if ( ln.prev == null )
-        {
-            // already the first node. or not a node
-            return;
-        }
-        // splice: remove it from the list
-        ln.prev.next = ln.next;
-
-        if ( ln.next == null )
-        {
-            // last but not the first.
-            last = (T) ln.prev;
-            last.next = null;
-        }
-        else
-        {
-            // neither the last nor the first.
-            ln.next.prev = ln.prev;
-        }
-        first.prev = ln;
-        ln.next = first;
-        ln.prev = null;
-        first = ln;
-    }
-
-    /**
-     * Moves an existing node to the end of the link list.
-     * <p>
-     * @param ln The node to set as the head.
-     */
-    public synchronized void makeLast(T ln)
-    {
-        if ( ln.next == null )
-        {
-            // already the last node. or not a node
-            return;
-        }
-        // splice: remove it from the list
-        if ( ln.prev != null )
-        {
-            ln.prev.next = ln.next;
-        }
-        else
-        {
-            // first
-            first = last;
-        }
-
-        if ( last != null )
-        {
-            last.next = ln;
-        }
-        ln.prev = last;
-        ln.next = null;
-        last = ln;
-    }
-
-    /**
-     * Remove all of the elements from the linked list implementation.
-     */
-    public synchronized void removeAll()
-    {
-        for (T me = first; me != null; )
-        {
-            if ( me.prev != null )
-            {
-                me.prev = null;
-            }
-            T next = (T) me.next;
-            me = next;
-        }
-        first = last = null;
-        // make sure this will work, could be add while this is happening.
-        size = 0;
-    }
-
-    /**
-     * Removes the specified node from the link list.
-     * <p>
-     * @param me Description of the Parameter
-     * @return true if an element was removed.
-     */
-    public synchronized boolean remove(T me)
-    {
-        log.debug( "removing node" );
-
-        if ( me.next == null )
-        {
-            if ( me.prev == null )
-            {
-                // Make sure it really is the only node before setting head and
-                // tail to null. It is possible that we will be passed a node
-                // which has already been removed from the list, in which case
-                // we should ignore it
-
-                if ( me == first && me == last )
-                {
-                    first = last = null;
-                }
-            }
-            else
-            {
-                // last but not the first.
-                last = (T) me.prev;
-                last.next = null;
-                me.prev = null;
-            }
-        }
-        else if ( me.prev == null )
-        {
-            // first but not the last.
-            first = (T) me.next;
-            first.prev = null;
-            me.next = null;
-        }
-        else
-        {
-            // neither the first nor the last.
-            me.prev.next = me.next;
-            me.next.prev = me.prev;
-            me.prev = me.next = null;
-        }
-        size--;
-
-        return true;
-    }
-
-    /**
-     * Removes the specified node from the link list.
-     * <p>
-     * @return The last node if there was one to remove.
-     */
-    public synchronized T removeLast()
-    {
-        log.debug( "removing last node" );
-        T temp = last;
-        if ( last != null )
-        {
-            remove( last );
-        }
-        return temp;
-    }
-
-    /**
-     * Returns the size of the list.
-     * <p>
-     * @return int
-     */
-    public synchronized int size()
-    {
-        return size;
-    }
-
-    // ///////////////////////////////////////////////////////////////////
-    /**
-     * Dump the cache entries from first to list for debugging.
-     */
-    public synchronized void debugDumpEntries()
-    {
-        if ( log.isDebugEnabled() )
-        {
-            log.debug( "dumping Entries" );
-            for (T me = first; me != null; me = (T) me.next)
-            {
-                log.debug( "dump Entries> payload= \"{0}\"", me.getPayload() );
-            }
-        }
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/struct/DoubleLinkedListNode.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/struct/DoubleLinkedListNode.java
deleted file mode 100644
index 26d080e..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/struct/DoubleLinkedListNode.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package org.apache.commons.jcs.utils.struct;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.Serializable;
-
-/**
- * This serves as a placeholder in a double linked list. You can extend this to
- * add functionality. This allows you to remove in constant time from a linked
- * list.
- * <p>
- * It simply holds the payload and a reference to the items before and after it
- * in the list.
- */
-public class DoubleLinkedListNode<T>
-    implements Serializable
-{
-    /** Dont' change. */
-    private static final long serialVersionUID = -1114934407695836097L;
-
-    /** The object in the node. */
-    private final T payload;
-
-    /** Double Linked list references */
-    public DoubleLinkedListNode<T> prev;
-
-    /** Double Linked list references */
-    public DoubleLinkedListNode<T> next;
-
-    /**
-     * @param payloadP
-     */
-    public DoubleLinkedListNode(T payloadP)
-    {
-        payload = payloadP;
-    }
-
-    /**
-     * @return Object
-     */
-    public T getPayload()
-    {
-        return payload;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/struct/LRUElementDescriptor.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/struct/LRUElementDescriptor.java
deleted file mode 100644
index f728466..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/struct/LRUElementDescriptor.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package org.apache.commons.jcs.utils.struct;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * This is a node in the double linked list. It is stored as the value in the underlying map used by
- * the LRUMap class.
- */
-public class LRUElementDescriptor<K, V>
-    extends DoubleLinkedListNode<V>
-{
-    /** Don't change. */
-    private static final long serialVersionUID = 8249555756363020156L;
-
-    /** The key value */
-    private K key;
-
-    /**
-     * @param key
-     * @param payloadP
-     */
-    public LRUElementDescriptor(K key, V payloadP)
-    {
-        super(payloadP);
-        this.setKey(key);
-    }
-
-    /**
-     * @param key The key to set.
-     */
-    public void setKey(K key)
-    {
-        this.key = key;
-    }
-
-    /**
-     * @return Returns the key.
-     */
-    public K getKey()
-    {
-        return key;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/struct/LRUMap.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/struct/LRUMap.java
deleted file mode 100644
index 84ff231..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/struct/LRUMap.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package org.apache.commons.jcs.utils.struct;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- *
- * @author Wiktor Niesiobędzki
- *
- *         Simple LRUMap implementation that keeps the number of the objects below or equal maxObjects
- *
- * @param <K>
- * @param <V>
- */
-public class LRUMap<K, V> extends AbstractLRUMap<K, V>
-{
-    /** if the max is less than 0, there is no limit! */
-    private int maxObjects = -1;
-
-    public LRUMap()
-    {
-        super();
-    }
-
-    /**
-     *
-     * @param maxObjects
-     *            maximum number to keep in the map
-     */
-    public LRUMap(int maxObjects)
-    {
-        this();
-        this.maxObjects = maxObjects;
-    }
-
-    @Override
-    public boolean shouldRemove()
-    {
-        return maxObjects > 0 && this.size() > maxObjects;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/threadpool/DaemonThreadFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/threadpool/DaemonThreadFactory.java
deleted file mode 100644
index e1ec271..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/threadpool/DaemonThreadFactory.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package org.apache.commons.jcs.utils.threadpool;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.concurrent.ThreadFactory;
-
-/**
- * Allows us to set the daemon status on the threads.
- * <p>
- * @author aaronsm
- */
-public class DaemonThreadFactory
-    implements ThreadFactory
-{
-    private String prefix;
-    private boolean threadIsDaemon = true;
-    private int threadPriority = Thread.NORM_PRIORITY;
-
-    /**
-     * Constructor
-     *
-     * @param prefix thread name prefix
-     */
-    public DaemonThreadFactory(String prefix)
-    {
-        this(prefix, Thread.NORM_PRIORITY);
-    }
-
-    /**
-     * Constructor
-     *
-     * @param prefix thread name prefix
-     * @param threadPriority set thread priority
-     */
-    public DaemonThreadFactory(String prefix, int threadPriority)
-    {
-        this.prefix = prefix;
-        this.threadPriority = threadPriority;
-    }
-
-    /**
-     * Sets the thread to daemon.
-     * <p>
-     * @param runner
-     * @return a daemon thread
-     */
-    @Override
-    public Thread newThread( Runnable runner )
-    {
-        Thread t = new Thread( runner );
-        String oldName = t.getName();
-        t.setName( prefix + oldName );
-        t.setDaemon(threadIsDaemon);
-        t.setPriority(threadPriority);
-        return t;
-    }
-}
\ No newline at end of file
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/threadpool/PoolConfiguration.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/threadpool/PoolConfiguration.java
deleted file mode 100644
index f4e886d..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/threadpool/PoolConfiguration.java
+++ /dev/null
@@ -1,293 +0,0 @@
-package org.apache.commons.jcs.utils.threadpool;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * This object holds configuration data for a thread pool.
- * <p>
- * @author Aaron Smuts
- */
-public final class PoolConfiguration
-    implements Cloneable
-{
-    /**
-     * DEFAULT SETTINGS
-     */
-    private static final boolean DEFAULT_USE_BOUNDARY = true;
-
-    /** Default queue size limit */
-    private static final int DEFAULT_BOUNDARY_SIZE = 2000;
-
-    /** Default max size */
-    private static final int DEFAULT_MAXIMUM_POOL_SIZE = 150;
-
-    /** Default min */
-    private static final int DEFAULT_MINIMUM_POOL_SIZE = Runtime.getRuntime().availableProcessors();
-
-    /** Default keep alive */
-    private static final int DEFAULT_KEEPALIVE_TIME = 1000 * 60 * 5;
-
-    /** Default when blocked */
-    private static final WhenBlockedPolicy DEFAULT_WHEN_BLOCKED_POLICY = WhenBlockedPolicy.RUN;
-
-    /** Default startup size */
-    private static final int DEFAULT_STARTUP_SIZE = DEFAULT_MINIMUM_POOL_SIZE;
-
-    /** Should we bound the queue */
-    private boolean useBoundary = DEFAULT_USE_BOUNDARY;
-
-    /** If the queue is bounded, how big can it get */
-    private int boundarySize = DEFAULT_BOUNDARY_SIZE;
-
-    /** only has meaning if a boundary is used */
-    private int maximumPoolSize = DEFAULT_MAXIMUM_POOL_SIZE;
-
-    /**
-     * the exact number that will be used in a boundless queue. If the queue has a boundary, more
-     * will be created if the queue fills.
-     */
-    private int minimumPoolSize = DEFAULT_MINIMUM_POOL_SIZE;
-
-    /** How long idle threads above the minimum should be kept alive. */
-    private int keepAliveTime = DEFAULT_KEEPALIVE_TIME;
-
-    public enum WhenBlockedPolicy {
-        /** abort when queue is full and max threads is reached. */
-        ABORT,
-
-        /** block when queue is full and max threads is reached. */
-        BLOCK,
-
-        /** run in current thread when queue is full and max threads is reached. */
-        RUN,
-
-        /** wait when queue is full and max threads is reached. */
-        WAIT,
-
-        /** discard oldest when queue is full and max threads is reached. */
-        DISCARDOLDEST
-    }
-
-    /** should be ABORT, BLOCK, RUN, WAIT, DISCARDOLDEST, */
-    private WhenBlockedPolicy whenBlockedPolicy = DEFAULT_WHEN_BLOCKED_POLICY;
-
-    /** The number of threads to create on startup */
-    private int startUpSize = DEFAULT_MINIMUM_POOL_SIZE;
-
-    /**
-     * @param useBoundary The useBoundary to set.
-     */
-    public void setUseBoundary( boolean useBoundary )
-    {
-        this.useBoundary = useBoundary;
-    }
-
-    /**
-     * @return Returns the useBoundary.
-     */
-    public boolean isUseBoundary()
-    {
-        return useBoundary;
-    }
-
-    /**
-     * Default
-     */
-    public PoolConfiguration()
-    {
-        this( DEFAULT_USE_BOUNDARY, DEFAULT_BOUNDARY_SIZE, DEFAULT_MAXIMUM_POOL_SIZE,
-              DEFAULT_MINIMUM_POOL_SIZE, DEFAULT_KEEPALIVE_TIME,
-              DEFAULT_WHEN_BLOCKED_POLICY, DEFAULT_STARTUP_SIZE );
-    }
-
-    /**
-     * Construct a completely configured instance.
-     * <p>
-     * @param useBoundary
-     * @param boundarySize
-     * @param maximumPoolSize
-     * @param minimumPoolSize
-     * @param keepAliveTime
-     * @param whenBlockedPolicy
-     * @param startUpSize
-     */
-    public PoolConfiguration( boolean useBoundary, int boundarySize, int maximumPoolSize, int minimumPoolSize,
-                              int keepAliveTime, WhenBlockedPolicy whenBlockedPolicy, int startUpSize )
-    {
-        setUseBoundary( useBoundary );
-        setBoundarySize( boundarySize );
-        setMaximumPoolSize( maximumPoolSize );
-        setMinimumPoolSize( minimumPoolSize );
-        setKeepAliveTime( keepAliveTime );
-        setWhenBlockedPolicy( whenBlockedPolicy );
-        setStartUpSize( startUpSize );
-    }
-
-    /**
-     * @param boundarySize The boundarySize to set.
-     */
-    public void setBoundarySize( int boundarySize )
-    {
-        this.boundarySize = boundarySize;
-    }
-
-    /**
-     * @return Returns the boundarySize.
-     */
-    public int getBoundarySize()
-    {
-        return boundarySize;
-    }
-
-    /**
-     * @param maximumPoolSize The maximumPoolSize to set.
-     */
-    public void setMaximumPoolSize( int maximumPoolSize )
-    {
-        this.maximumPoolSize = maximumPoolSize;
-    }
-
-    /**
-     * @return Returns the maximumPoolSize.
-     */
-    public int getMaximumPoolSize()
-    {
-        return maximumPoolSize;
-    }
-
-    /**
-     * @param minimumPoolSize The minimumPoolSize to set.
-     */
-    public void setMinimumPoolSize( int minimumPoolSize )
-    {
-        this.minimumPoolSize = minimumPoolSize;
-    }
-
-    /**
-     * @return Returns the minimumPoolSize.
-     */
-    public int getMinimumPoolSize()
-    {
-        return minimumPoolSize;
-    }
-
-    /**
-     * @param keepAliveTime The keepAliveTime to set.
-     */
-    public void setKeepAliveTime( int keepAliveTime )
-    {
-        this.keepAliveTime = keepAliveTime;
-    }
-
-    /**
-     * @return Returns the keepAliveTime.
-     */
-    public int getKeepAliveTime()
-    {
-        return keepAliveTime;
-    }
-
-    /**
-     * @param whenBlockedPolicy The whenBlockedPolicy to set.
-     */
-    public void setWhenBlockedPolicy( String whenBlockedPolicy )
-    {
-        if ( whenBlockedPolicy != null )
-        {
-            WhenBlockedPolicy policy = WhenBlockedPolicy.valueOf(whenBlockedPolicy.trim().toUpperCase());
-            setWhenBlockedPolicy(policy);
-        }
-        else
-        {
-            // the value is null, default to RUN
-            this.whenBlockedPolicy = WhenBlockedPolicy.RUN;
-        }
-    }
-
-    /**
-     * @param whenBlockedPolicy The whenBlockedPolicy to set.
-     */
-    public void setWhenBlockedPolicy( WhenBlockedPolicy whenBlockedPolicy )
-    {
-        if ( whenBlockedPolicy != null )
-        {
-            this.whenBlockedPolicy = whenBlockedPolicy;
-        }
-        else
-        {
-            // the value is null, default to RUN
-            this.whenBlockedPolicy = WhenBlockedPolicy.RUN;
-        }
-    }
-
-    /**
-     * @return Returns the whenBlockedPolicy.
-     */
-    public WhenBlockedPolicy getWhenBlockedPolicy()
-    {
-        return whenBlockedPolicy;
-    }
-
-    /**
-     * @param startUpSize The startUpSize to set.
-     */
-    public void setStartUpSize( int startUpSize )
-    {
-        this.startUpSize = startUpSize;
-    }
-
-    /**
-     * @return Returns the startUpSize.
-     */
-    public int getStartUpSize()
-    {
-        return startUpSize;
-    }
-
-    /**
-     * To string for debugging purposes.
-     * @return String
-     */
-    @Override
-    public String toString()
-    {
-        StringBuilder buf = new StringBuilder();
-        buf.append( "useBoundary = [" + isUseBoundary() + "] " );
-        buf.append( "boundarySize = [" + boundarySize + "] " );
-        buf.append( "maximumPoolSize = [" + maximumPoolSize + "] " );
-        buf.append( "minimumPoolSize = [" + minimumPoolSize + "] " );
-        buf.append( "keepAliveTime = [" + keepAliveTime + "] " );
-        buf.append( "whenBlockedPolicy = [" + getWhenBlockedPolicy() + "] " );
-        buf.append( "startUpSize = [" + startUpSize + "]" );
-        return buf.toString();
-    }
-
-    /**
-     * Copies the instance variables to another instance.
-     * <p>
-     * @return PoolConfiguration
-     */
-    @Override
-    public PoolConfiguration clone()
-    {
-        return new PoolConfiguration( isUseBoundary(), boundarySize, maximumPoolSize, minimumPoolSize, keepAliveTime,
-                                      getWhenBlockedPolicy(), startUpSize );
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/threadpool/ThreadPoolManager.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/threadpool/ThreadPoolManager.java
deleted file mode 100644
index 3c143be..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/threadpool/ThreadPoolManager.java
+++ /dev/null
@@ -1,338 +0,0 @@
-package org.apache.commons.jcs.utils.threadpool;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-import org.apache.commons.jcs.utils.config.PropertySetter;
-
-/**
- * This manages threadpools for an application
- * <p>
- * It is a singleton since threads need to be managed vm wide.
- * <p>
- * This manager forces you to use a bounded queue. By default it uses the current thread for
- * execution when the buffer is full and no free threads can be created.
- * <p>
- * You can specify the props file to use or pass in a properties object prior to configuration.
- * <p>
- * If set, the Properties object will take precedence.
- * <p>
- * If a value is not set for a particular pool, the hard coded defaults in <code>PoolConfiguration</code> will be used.
- * You can configure default settings by specifying <code>thread_pool.default</code> in the properties, ie "cache.ccf"
- * <p>
- * @author Aaron Smuts
- */
-public class ThreadPoolManager
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog( ThreadPoolManager.class );
-
-    /** The default config, created using property defaults if present, else those above. */
-    private PoolConfiguration defaultConfig;
-
-    /** The default scheduler config, created using property defaults if present, else those above. */
-    private PoolConfiguration defaultSchedulerConfig;
-
-    /** the root property name */
-    private static final String PROP_NAME_ROOT = "thread_pool";
-
-    /** default property file name */
-    private static final String DEFAULT_PROP_NAME_ROOT = "thread_pool.default";
-
-    /** the scheduler root property name */
-    private static final String PROP_NAME_SCHEDULER_ROOT = "scheduler_pool";
-
-    /** default scheduler property file name */
-    private static final String DEFAULT_PROP_NAME_SCHEDULER_ROOT = "scheduler_pool.default";
-
-   /**
-     * You can specify the properties to be used to configure the thread pool. Setting this post
-     * initialization will have no effect.
-     */
-    private static volatile Properties props = null;
-
-    /** Map of names to pools. */
-    private ConcurrentHashMap<String, ExecutorService> pools;
-
-    /** Map of names to scheduler pools. */
-    private ConcurrentHashMap<String, ScheduledExecutorService> schedulerPools;
-
-    /**
-     * The ThreadPoolManager instance (holder pattern)
-     */
-    private static class ThreadPoolManagerHolder
-    {
-        static final ThreadPoolManager INSTANCE = new ThreadPoolManager();
-    }
-
-    /**
-     * No instances please. This is a singleton.
-     */
-    private ThreadPoolManager()
-    {
-        this.pools = new ConcurrentHashMap<>();
-        this.schedulerPools = new ConcurrentHashMap<>();
-        configure();
-    }
-
-    /**
-     * Creates a pool based on the configuration info.
-     * <p>
-     * @param config the pool configuration
-     * @param threadNamePrefix prefix for the thread names of the pool
-     * @return A ThreadPool wrapper
-     */
-    public ExecutorService createPool( PoolConfiguration config, String threadNamePrefix)
-    {
-    	return createPool(config, threadNamePrefix, Thread.NORM_PRIORITY);
-    }
-
-    /**
-     * Creates a pool based on the configuration info.
-     * <p>
-     * @param config the pool configuration
-     * @param threadNamePrefix prefix for the thread names of the pool
-     * @param threadPriority the priority of the created threads
-     * @return A ThreadPool wrapper
-     */
-    public ExecutorService createPool( PoolConfiguration config, String threadNamePrefix, int threadPriority )
-    {
-        BlockingQueue<Runnable> queue = null;
-        if ( config.isUseBoundary() )
-        {
-            log.debug( "Creating a Bounded Buffer to use for the pool" );
-            queue = new LinkedBlockingQueue<>(config.getBoundarySize());
-        }
-        else
-        {
-            log.debug( "Creating a non bounded Linked Queue to use for the pool" );
-            queue = new LinkedBlockingQueue<>();
-        }
-
-        ThreadPoolExecutor pool = new ThreadPoolExecutor(
-            config.getStartUpSize(),
-            config.getMaximumPoolSize(),
-            config.getKeepAliveTime(),
-            TimeUnit.MILLISECONDS,
-            queue,
-            new DaemonThreadFactory(threadNamePrefix, threadPriority));
-
-        // when blocked policy
-        switch (config.getWhenBlockedPolicy())
-        {
-            case ABORT:
-                pool.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
-                break;
-
-            case RUN:
-                pool.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
-                break;
-
-            case WAIT:
-                throw new RuntimeException("POLICY_WAIT no longer supported");
-
-            case DISCARDOLDEST:
-                pool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
-                break;
-
-            default:
-                break;
-        }
-
-        pool.prestartAllCoreThreads();
-
-        return pool;
-    }
-
-    /**
-     * Creates a scheduler pool based on the configuration info.
-     * <p>
-     * @param config the pool configuration
-     * @param threadNamePrefix prefix for the thread names of the pool
-     * @param threadPriority the priority of the created threads
-     * @return A ScheduledExecutorService
-     */
-    public ScheduledExecutorService createSchedulerPool( PoolConfiguration config, String threadNamePrefix, int threadPriority )
-    {
-    	ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(
-    			config.getMaximumPoolSize(),
-    			new DaemonThreadFactory(threadNamePrefix, threadPriority));
-
-        return scheduler;
-    }
-
-    /**
-     * Returns a configured instance of the ThreadPoolManger To specify a configuration file or
-     * Properties object to use call the appropriate setter prior to calling getInstance.
-     * <p>
-     * @return The single instance of the ThreadPoolManager
-     */
-    public static ThreadPoolManager getInstance()
-    {
-        return ThreadPoolManagerHolder.INSTANCE;
-    }
-
-    /**
-     * Dispose of the instance of the ThreadPoolManger and shut down all thread pools
-     */
-    public static void dispose()
-    {
-        for ( Iterator<Map.Entry<String, ExecutorService>> i =
-                getInstance().pools.entrySet().iterator(); i.hasNext(); )
-        {
-            Map.Entry<String, ExecutorService> entry = i.next();
-            try
-            {
-                entry.getValue().shutdownNow();
-            }
-            catch (Throwable t)
-            {
-                log.warn("Failed to close pool {0}", entry.getKey(), t);
-            }
-            i.remove();
-        }
-
-        for ( Iterator<Map.Entry<String, ScheduledExecutorService>> i =
-                getInstance().schedulerPools.entrySet().iterator(); i.hasNext(); )
-        {
-            Map.Entry<String, ScheduledExecutorService> entry = i.next();
-            try
-            {
-                entry.getValue().shutdownNow();
-            }
-            catch (Throwable t)
-            {
-                log.warn("Failed to close pool {0}", entry.getKey(), t);
-            }
-            i.remove();
-        }
-    }
-
-    /**
-     * Returns an executor service by name. If a service by this name does not exist in the configuration file or
-     * properties, one will be created using the default values.
-     * <p>
-     * Services are lazily created.
-     * <p>
-     * @param name
-     * @return The executor service configured for the name.
-     */
-    public ExecutorService getExecutorService( String name )
-    {
-    	ExecutorService pool = pools.computeIfAbsent(name, key -> {
-            log.debug( "Creating pool for name [{0}]", key );
-            PoolConfiguration config = loadConfig( PROP_NAME_ROOT + "." + key, defaultConfig );
-            return createPool( config, "JCS-ThreadPoolManager-" + key + "-" );
-    	});
-
-        return pool;
-    }
-
-    /**
-     * Returns a scheduler pool by name. If a pool by this name does not exist in the configuration file or
-     * properties, one will be created using the default values.
-     * <p>
-     * Pools are lazily created.
-     * <p>
-     * @param name
-     * @return The scheduler pool configured for the name.
-     */
-    public ScheduledExecutorService getSchedulerPool( String name )
-    {
-    	ScheduledExecutorService pool = schedulerPools.computeIfAbsent(name, key -> {
-            log.debug( "Creating scheduler pool for name [{0}]", key );
-            PoolConfiguration config = loadConfig( PROP_NAME_SCHEDULER_ROOT + "." + key,
-                    defaultSchedulerConfig );
-            return createSchedulerPool( config, "JCS-ThreadPoolManager-" + key + "-", Thread.NORM_PRIORITY );
-    	});
-
-        return pool;
-    }
-
-    /**
-     * Returns the names of all configured pools.
-     * <p>
-     * @return ArrayList of string names
-     */
-    protected Set<String> getPoolNames()
-    {
-        return pools.keySet();
-    }
-
-    /**
-     * This will be used if it is not null on initialization. Setting this post initialization will
-     * have no effect.
-     * <p>
-     * @param props The props to set.
-     */
-    public static void setProps( Properties props )
-    {
-        ThreadPoolManager.props = props;
-    }
-
-    /**
-     * Initialize the ThreadPoolManager and create all the pools defined in the configuration.
-     */
-    private void configure()
-    {
-        log.debug( "Initializing ThreadPoolManager" );
-
-        if ( props == null )
-        {
-            log.warn( "No configuration settings found. Using hardcoded default values for all pools." );
-            props = new Properties();
-        }
-
-        // set initial default and then override if new settings are available
-        defaultConfig = loadConfig( DEFAULT_PROP_NAME_ROOT, new PoolConfiguration() );
-        defaultSchedulerConfig = loadConfig( DEFAULT_PROP_NAME_SCHEDULER_ROOT, new PoolConfiguration() );
-    }
-
-    /**
-     * Configures the PoolConfiguration settings.
-     * <p>
-     * @param root the configuration key prefix
-     * @param defaultPoolConfiguration the default configuration
-     * @return PoolConfiguration
-     */
-    private PoolConfiguration loadConfig( String root, PoolConfiguration defaultPoolConfiguration )
-    {
-        PoolConfiguration config = defaultPoolConfiguration.clone();
-        PropertySetter.setProperties( config, props, root + "." );
-
-        log.debug( "{0} PoolConfiguration = {1}", root, config );
-
-        return config;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/timing/ElapsedTimer.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/timing/ElapsedTimer.java
deleted file mode 100644
index bd728e9..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/timing/ElapsedTimer.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package org.apache.commons.jcs.utils.timing;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * This is a simple timer utility.
- */
-public class ElapsedTimer
-{
-    /** display suffix describing the unit of measure. */
-    private static final String SUFFIX = "ms.";
-
-    /**
-     * Sets the start time when created.
-     */
-    private long timeStamp = System.currentTimeMillis();
-
-    /**
-     * Gets the time elapsed between the start time and now. The start time is reset to now.
-     * Subsequent calls will get the time between then and now.
-     * <p>
-     * @return the elapsed time
-     */
-    public long getElapsedTime()
-    {
-        long now = System.currentTimeMillis();
-        long elapsed = now - timeStamp;
-        timeStamp = now;
-        return elapsed;
-    }
-
-    /**
-     * Returns the elapsed time with the display suffix.
-     * <p>
-     * @return formatted elapsed Time
-     */
-    public String getElapsedTimeString()
-    {
-        return String.valueOf( getElapsedTime() ) + SUFFIX;
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/zip/CompressionUtil.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/zip/CompressionUtil.java
deleted file mode 100644
index 5ef5954..0000000
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/zip/CompressionUtil.java
+++ /dev/null
@@ -1,203 +0,0 @@
-package org.apache.commons.jcs.utils.zip;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.util.zip.DataFormatException;
-import java.util.zip.Deflater;
-import java.util.zip.GZIPInputStream;
-import java.util.zip.Inflater;
-
-/** Compress / Decompress. */
-public final class CompressionUtil
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog( CompressionUtil.class );
-
-    /**
-     * no instances.
-     */
-    private CompressionUtil()
-    {
-        // NO OP
-    }
-
-    /**
-     * Decompress the byte array passed using a default buffer length of 1024.
-     * <p>
-     * @param input compressed byte array webservice response
-     * @return uncompressed byte array
-     */
-    public static byte[] decompressByteArray( final byte[] input )
-    {
-        return decompressByteArray( input, 1024 );
-    }
-
-    /**
-     * Decompress the byte array passed
-     * <p>
-     * @param input compressed byte array webservice response
-     * @param bufferLength buffer length
-     * @return uncompressed byte array
-     */
-    public static byte[] decompressByteArray( final byte[] input, final int bufferLength )
-    {
-        if ( null == input )
-        {
-            throw new IllegalArgumentException( "Input was null" );
-        }
-
-        // Create the decompressor and give it the data to compress
-        final Inflater decompressor = new Inflater();
-
-        decompressor.setInput( input );
-
-        // Create an expandable byte array to hold the decompressed data
-        final ByteArrayOutputStream baos = new ByteArrayOutputStream( input.length );
-
-        // Decompress the data
-        final byte[] buf = new byte[bufferLength];
-
-        try
-        {
-            while ( !decompressor.finished() )
-            {
-                int count = decompressor.inflate( buf );
-                baos.write( buf, 0, count );
-            }
-        }
-        catch ( DataFormatException ex )
-        {
-            log.error( "Problem decompressing.", ex );
-        }
-
-        decompressor.end();
-
-        try
-        {
-            baos.close();
-        }
-        catch ( IOException ex )
-        {
-            log.error( "Problem closing stream.", ex );
-        }
-
-        return baos.toByteArray();
-    }
-
-    /**
-     * Compress the byte array passed
-     * <p>
-     * @param input byte array
-     * @return compressed byte array
-     * @throws IOException thrown if we can't close the output stream
-     */
-    public static byte[] compressByteArray( byte[] input )
-        throws IOException
-    {
-        return compressByteArray( input, 1024 );
-    }
-
-    /**
-     * Compress the byte array passed
-     * <p>
-     * @param input byte array
-     * @param bufferLength buffer length
-     * @return compressed byte array
-     * @throws IOException thrown if we can't close the output stream
-     */
-    public static byte[] compressByteArray( byte[] input, int bufferLength )
-        throws IOException
-    {
-        // Compressor with highest level of compression
-        Deflater compressor = new Deflater();
-        compressor.setLevel( Deflater.BEST_COMPRESSION );
-
-        // Give the compressor the data to compress
-        compressor.setInput( input );
-        compressor.finish();
-
-        // Create an expandable byte array to hold the compressed data.
-        // It is not necessary that the compressed data will be smaller than
-        // the uncompressed data.
-        ByteArrayOutputStream bos = new ByteArrayOutputStream( input.length );
-
-        // Compress the data
-        byte[] buf = new byte[bufferLength];
-        while ( !compressor.finished() )
-        {
-            int count = compressor.deflate( buf );
-            bos.write( buf, 0, count );
-        }
-
-        // JCS-136 ( Details here : http://www.devguli.com/blog/eng/java-deflater-and-outofmemoryerror/ )
-        compressor.end();
-        bos.close();
-
-        // Get the compressed data
-        return bos.toByteArray();
-
-    }
-
-    /**
-     * decompress a gzip byte array, using a default buffer length of 1024
-     * <p>
-     * @param compressedByteArray gzip-compressed byte array
-     * @return decompressed byte array
-     * @throws IOException thrown if there was a failure to construct the GzipInputStream
-     */
-    public static byte[] decompressGzipByteArray( byte[] compressedByteArray )
-        throws IOException
-    {
-        return decompressGzipByteArray( compressedByteArray, 1024 );
-    }
-
-    /**
-     * decompress a gzip byte array, using a default buffer length of 1024
-     * <p>
-     * @param compressedByteArray gzip-compressed byte array
-     * @param bufferlength size of the buffer in bytes
-     * @return decompressed byte array
-     * @throws IOException thrown if there was a failure to construct the GzipInputStream
-     */
-    public static byte[] decompressGzipByteArray( byte[] compressedByteArray, int bufferlength )
-        throws IOException
-    {
-        ByteArrayOutputStream uncompressedStream = new ByteArrayOutputStream();
-
-        GZIPInputStream compressedStream = new GZIPInputStream( new ByteArrayInputStream( compressedByteArray ) );
-
-        byte[] buffer = new byte[bufferlength];
-
-        int index = -1;
-
-        while ( ( index = compressedStream.read( buffer ) ) != -1 )
-        {
-            uncompressedStream.write( buffer, 0, index );
-        }
-
-        return uncompressedStream.toByteArray();
-    }
-}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/JCS.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/JCS.java
new file mode 100644
index 0000000..e0dd191
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/JCS.java
@@ -0,0 +1,210 @@
+package org.apache.commons.jcs3;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Properties;
+
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.access.GroupCacheAccess;
+import org.apache.commons.jcs3.access.exception.CacheException;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes;
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+import org.apache.commons.jcs3.engine.control.CompositeCacheManager;
+import org.apache.commons.jcs3.engine.control.group.GroupAttrName;
+
+/**
+ * Simple class for using JCS. To use JCS in your application, you can use the static methods of
+ * this class to get access objects (instances of this class) for your cache regions. One CacheAccess
+ * object should be created for each region you want to access. If you have several regions, then
+ * get instances for each. For best performance the getInstance call should be made in an
+ * initialization method.
+ */
+public abstract class JCS
+{
+    /** cache.ccf alternative. */
+    private static String configFilename = null;
+
+    /** alternative configuration properties */
+    private static Properties configProps = null;
+
+    /** Cache manager use by the various forms of defineRegion and getAccess */
+    private static CompositeCacheManager cacheMgr;
+
+    /**
+     * Set the filename that the cache manager will be initialized with. Only matters before the
+     * instance is initialized.
+     * <p>
+     * @param configFilename
+     */
+    public static void setConfigFilename( String configFilename )
+    {
+        JCS.configFilename = configFilename;
+    }
+
+    /**
+     * Set the properties that the cache manager will be initialized with. Only
+     * matters before the instance is initialized.
+     *
+     * @param configProps
+     */
+    public static void setConfigProperties( Properties configProps )
+    {
+        JCS.configProps = configProps;
+    }
+
+    /**
+     * Shut down the cache manager and set the instance to null
+     */
+    public static void shutdown()
+    {
+        synchronized ( JCS.class )
+        {
+            if ( cacheMgr != null && cacheMgr.isInitialized())
+            {
+            	cacheMgr.shutDown();
+            }
+
+            cacheMgr = null;
+        }
+    }
+
+    /**
+     * Helper method which checks to make sure the cacheMgr class field is set, and if not requests
+     * an instance from CacheManagerFactory.
+     *
+     * @throws CacheException if the configuration cannot be loaded
+     */
+    private static CompositeCacheManager getCacheManager() throws CacheException
+    {
+        synchronized ( JCS.class )
+        {
+            if ( cacheMgr == null || !cacheMgr.isInitialized())
+            {
+                if ( configProps != null )
+                {
+                    cacheMgr = CompositeCacheManager.getUnconfiguredInstance();
+                    cacheMgr.configure( configProps );
+                }
+                else if ( configFilename != null )
+                {
+                    cacheMgr = CompositeCacheManager.getUnconfiguredInstance();
+                    cacheMgr.configure( configFilename );
+                }
+                else
+                {
+                    cacheMgr = CompositeCacheManager.getInstance();
+                }
+            }
+
+            return cacheMgr;
+        }
+    }
+
+    /**
+     * Get a CacheAccess which accesses the provided region.
+     * <p>
+     * @param region Region that return CacheAccess will provide access to
+     * @return A CacheAccess which provides access to a given region.
+     * @throws CacheException
+     */
+    public static <K, V> CacheAccess<K, V> getInstance( String region )
+        throws CacheException
+    {
+        CompositeCache<K, V> cache = getCacheManager().getCache( region );
+        return new CacheAccess<>( cache );
+    }
+
+    /**
+     * Get a CacheAccess which accesses the provided region.
+     * <p>
+     * @param region Region that return CacheAccess will provide access to
+     * @param icca CacheAttributes for region
+     * @return A CacheAccess which provides access to a given region.
+     * @throws CacheException
+     */
+    public static <K, V> CacheAccess<K, V> getInstance( String region, ICompositeCacheAttributes icca )
+        throws CacheException
+    {
+        CompositeCache<K, V> cache = getCacheManager().getCache( region, icca );
+        return new CacheAccess<>( cache );
+    }
+
+    /**
+     * Get a CacheAccess which accesses the provided region.
+     * <p>
+     * @param region Region that return CacheAccess will provide access to
+     * @param icca CacheAttributes for region
+     * @param eattr ElementAttributes for the region
+     * @return A CacheAccess which provides access to a given region.
+     * @throws CacheException
+     */
+    public static <K, V> CacheAccess<K, V> getInstance( String region, ICompositeCacheAttributes icca,  IElementAttributes eattr )
+        throws CacheException
+    {
+        CompositeCache<K, V> cache = getCacheManager().getCache( region, icca, eattr );
+        return new CacheAccess<>( cache );
+    }
+
+    /**
+     * Get a GroupCacheAccess which accesses the provided region.
+     * <p>
+     * @param region Region that return GroupCacheAccess will provide access to
+     * @return A GroupCacheAccess which provides access to a given region.
+     * @throws CacheException
+     */
+    public static <K, V> GroupCacheAccess<K, V> getGroupCacheInstance( String region )
+        throws CacheException
+    {
+        CompositeCache<GroupAttrName<K>, V> cache = getCacheManager().getCache( region );
+        return new GroupCacheAccess<>( cache );
+    }
+
+    /**
+     * Get a GroupCacheAccess which accesses the provided region.
+     * <p>
+     * @param region Region that return GroupCacheAccess will provide access to
+     * @param icca CacheAttributes for region
+     * @return A GroupCacheAccess which provides access to a given region.
+     * @throws CacheException
+     */
+    public static <K, V> GroupCacheAccess<K, V> getGroupCacheInstance( String region, ICompositeCacheAttributes icca )
+        throws CacheException
+    {
+        CompositeCache<GroupAttrName<K>, V> cache = getCacheManager().getCache( region, icca );
+        return new GroupCacheAccess<>( cache );
+    }
+
+    /**
+     * Get a GroupCacheAccess which accesses the provided region.
+     * <p>
+     * @param region Region that return CacheAccess will provide access to
+     * @param icca CacheAttributes for region
+     * @param eattr ElementAttributes for the region
+     * @return A GroupCacheAccess which provides access to a given region.
+     * @throws CacheException
+     */
+    public static <K, V> GroupCacheAccess<K, V> getGroupCacheInstance( String region, ICompositeCacheAttributes icca,  IElementAttributes eattr )
+        throws CacheException
+    {
+        CompositeCache<GroupAttrName<K>, V> cache = getCacheManager().getCache( region, icca, eattr );
+        return new GroupCacheAccess<>( cache );
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/AbstractCacheAccess.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/AbstractCacheAccess.java
new file mode 100644
index 0000000..2c28555
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/AbstractCacheAccess.java
@@ -0,0 +1,203 @@
+package org.apache.commons.jcs3.access;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+
+import org.apache.commons.jcs3.access.behavior.ICacheAccessManagement;
+import org.apache.commons.jcs3.access.exception.CacheException;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes;
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+import org.apache.commons.jcs3.engine.stats.behavior.ICacheStats;
+
+/**
+ * This class provides the common methods for all types of access to the cache.
+ * <p>
+ * An instance of this class is tied to a specific cache region. Static methods are provided to get
+ * such instances.
+ * <p>
+ * Using this class you can retrieve an item, the item's wrapper, and the element's configuration.  You can also put an
+ * item in the cache, remove an item, and clear a region.
+ * <p>
+ * The JCS class is the preferred way to access these methods.
+ */
+public abstract class AbstractCacheAccess<K, V>
+    implements ICacheAccessManagement
+{
+    /**
+     * The cache that a given instance of this class provides access to.
+     * <p>
+     * TODO Should this be the interface?
+     */
+    private final CompositeCache<K, V> cacheControl;
+
+    /**
+     * Constructor for the CacheAccess object.
+     * <p>
+     * @param cacheControl The cache which the created instance accesses
+     */
+    protected AbstractCacheAccess( CompositeCache<K, V> cacheControl )
+    {
+        this.cacheControl = cacheControl;
+    }
+
+    /**
+     * Removes all of the elements from a region.
+     * <p>
+     * @throws CacheException
+     */
+    @Override
+    public void clear()
+        throws CacheException
+    {
+        try
+        {
+            this.getCacheControl().removeAll();
+        }
+        catch ( IOException e )
+        {
+            throw new CacheException( e );
+        }
+    }
+
+    /**
+     * This method is does not reset the attributes for items already in the cache. It could
+     * potentially do this for items in memory, and maybe on disk (which would be slow) but not
+     * remote items. Rather than have unpredictable behavior, this method just sets the default
+     * attributes. Items subsequently put into the cache will use these defaults if they do not
+     * specify specific attributes.
+     * <p>
+     * @param attr the default attributes.
+     * @throws CacheException if something goes wrong.
+     */
+    @Override
+    public void setDefaultElementAttributes( IElementAttributes attr )
+        throws CacheException
+    {
+        this.getCacheControl().setElementAttributes( attr );
+    }
+
+    /**
+     * Retrieves A COPY OF the default element attributes used by this region. This does not provide
+     * a reference to the element attributes.
+     * <p>
+     * Each time an element is added to the cache without element attributes, the default element
+     * attributes are cloned.
+     * <p>
+     * @return the default element attributes used by this region.
+     * @throws CacheException
+     */
+    @Override
+    public IElementAttributes getDefaultElementAttributes()
+        throws CacheException
+    {
+        return this.getCacheControl().getElementAttributes();
+    }
+
+    /**
+     * This returns the ICacheStats object with information on this region and its auxiliaries.
+     * <p>
+     * This data can be formatted as needed.
+     * <p>
+     * @return ICacheStats
+     */
+    @Override
+    public ICacheStats getStatistics()
+    {
+        return this.getCacheControl().getStatistics();
+    }
+
+    /**
+     * @return A String version of the stats.
+     */
+    @Override
+    public String getStats()
+    {
+        return this.getCacheControl().getStats();
+    }
+
+    /**
+     * Dispose this region. Flushes objects to and closes auxiliary caches. This is a shutdown
+     * command!
+     * <p>
+     * To simply remove all elements from the region use clear().
+     */
+    @Override
+    public void dispose()
+    {
+        this.getCacheControl().dispose();
+    }
+
+    /**
+     * Gets the ICompositeCacheAttributes of the cache region.
+     * <p>
+     * @return ICompositeCacheAttributes, the controllers config info, defined in the top section of
+     *         a region definition.
+     */
+    @Override
+    public ICompositeCacheAttributes getCacheAttributes()
+    {
+        return this.getCacheControl().getCacheAttributes();
+    }
+
+    /**
+     * Sets the ICompositeCacheAttributes of the cache region.
+     * <p>
+     * @param cattr The new ICompositeCacheAttribute value
+     */
+    @Override
+    public void setCacheAttributes( ICompositeCacheAttributes cattr )
+    {
+        this.getCacheControl().setCacheAttributes( cattr );
+    }
+
+    /**
+     * This instructs the memory cache to remove the <i>numberToFree</i> according to its eviction
+     * policy. For example, the LRUMemoryCache will remove the <i>numberToFree</i> least recently
+     * used items. These will be spooled to disk if a disk auxiliary is available.
+     * <p>
+     * @param numberToFree
+     * @return the number that were removed. if you ask to free 5, but there are only 3, you will
+     *         get 3.
+     * @throws CacheException
+     */
+    @Override
+    public int freeMemoryElements( int numberToFree )
+        throws CacheException
+    {
+        int numFreed = -1;
+        try
+        {
+            numFreed = this.getCacheControl().getMemoryCache().freeElements( numberToFree );
+        }
+        catch ( IOException ioe )
+        {
+            String message = "Failure freeing memory elements.";
+            throw new CacheException( message, ioe );
+        }
+        return numFreed;
+    }
+
+    public CompositeCache<K, V> getCacheControl() {
+        return cacheControl;
+    }
+
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/CacheAccess.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/CacheAccess.java
new file mode 100644
index 0000000..7bf290c
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/CacheAccess.java
@@ -0,0 +1,332 @@
+package org.apache.commons.jcs3.access;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import org.apache.commons.jcs3.access.behavior.ICacheAccess;
+import org.apache.commons.jcs3.access.exception.CacheException;
+import org.apache.commons.jcs3.access.exception.InvalidArgumentException;
+import org.apache.commons.jcs3.access.exception.InvalidHandleException;
+import org.apache.commons.jcs3.access.exception.ObjectExistsException;
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+
+/**
+ * This class provides an interface for all types of access to the cache.
+ * <p>
+ * An instance of this class is tied to a specific cache region. Static methods are provided to get
+ * such instances.
+ * <p>
+ * Using this class you can retrieve an item, the item's wrapper, and the element's configuration.  You can also put an
+ * item in the cache, remove an item, and clear a region.
+ * <p>
+ * The JCS class is the preferred way to access these methods.
+ */
+public class CacheAccess<K, V>
+    extends AbstractCacheAccess<K, V>
+    implements ICacheAccess<K, V>
+{
+    /**
+     * Constructor for the CacheAccess object.
+     * <p>
+     * @param cacheControl The cache which the created instance accesses
+     */
+    public CacheAccess( CompositeCache<K, V> cacheControl )
+    {
+        super(cacheControl);
+    }
+
+    /**
+     * Retrieve an object from the cache region this instance provides access to.
+     * <p>
+     * @param name Key the object is stored as
+     * @return The object if found or null
+     */
+    @Override
+    public V get( K name )
+    {
+        ICacheElement<K, V> element = this.getCacheControl().get( name );
+
+        return ( element != null ) ? element.getVal() : null;
+    }
+
+    /**
+     * Retrieve an object from the cache region this instance provides access to.
+     * If the object cannot be found in the cache, it will be retrieved by
+     * calling the supplier and subsequently storing it in the cache.
+     * <p>
+     * @param name
+     * @param supplier supplier to be called if the value is not found
+     * @return Object.
+     */
+    @Override
+    public V get(K name, Supplier<V> supplier)
+    {
+        V value = get(name);
+
+        if (value == null)
+        {
+            value = supplier.get();
+            put(name, value);
+        }
+
+        return value;
+    }
+
+    /**
+     * Retrieve matching objects from the cache region this instance provides access to.
+     * <p>
+     * @param pattern - a key pattern for the objects stored
+     * @return A map of key to values.  These are stripped from the wrapper.
+     */
+    @Override
+    public Map<K, V> getMatching( String pattern )
+    {
+        Map<K, V> unwrappedResults;
+
+        Map<K, ICacheElement<K, V>> wrappedResults = this.getCacheControl().getMatching( pattern );
+
+        if ( wrappedResults == null )
+        {
+            unwrappedResults = new HashMap<>();
+        }
+        else
+        {
+            unwrappedResults = wrappedResults.entrySet()
+                    .stream()
+                    .filter(entry -> entry.getValue() != null)
+                    .collect(Collectors.toMap(
+                            entry -> entry.getKey(),
+                            entry -> entry.getValue().getVal()));
+        }
+
+        return unwrappedResults;
+    }
+
+    /**
+     * This method returns the ICacheElement&lt;K, V&gt; wrapper which provides access to element info and other
+     * attributes.
+     * <p>
+     * This returns a reference to the wrapper. Any modifications will be reflected in the cache. No
+     * defensive copy is made.
+     * <p>
+     * This method is most useful if you want to determine things such as the how long the element
+     * has been in the cache.
+     * <p>
+     * The last access time in the ElementAttributes should be current.
+     * <p>
+     * @param name Key the Serializable is stored as
+     * @return The ICacheElement&lt;K, V&gt; if the object is found or null
+     */
+    @Override
+    public ICacheElement<K, V> getCacheElement( K name )
+    {
+        return this.getCacheControl().get( name );
+    }
+
+    /**
+     * Get multiple elements from the cache based on a set of cache keys.
+     * <p>
+     * This method returns the ICacheElement&lt;K, V&gt; wrapper which provides access to element info and other
+     * attributes.
+     * <p>
+     * This returns a reference to the wrapper. Any modifications will be reflected in the cache. No
+     * defensive copy is made.
+     * <p>
+     * This method is most useful if you want to determine things such as the how long the element
+     * has been in the cache.
+     * <p>
+     * The last access time in the ElementAttributes should be current.
+     * <p>
+     * @param names set of Serializable cache keys
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or empty map if none of the keys are present
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getCacheElements( Set<K> names )
+    {
+        return this.getCacheControl().getMultiple( names );
+    }
+
+    /**
+     * Get multiple elements from the cache based on a set of cache keys.
+     * <p>
+     * This method returns the ICacheElement&lt;K, V&gt; wrapper which provides access to element info and other
+     * attributes.
+     * <p>
+     * This returns a reference to the wrapper. Any modifications will be reflected in the cache. No
+     * defensive copy is made.
+     * <p>
+     * This method is most useful if you want to determine things such as the how long the element
+     * has been in the cache.
+     * <p>
+     * The last access time in the ElementAttributes should be current.
+     * <p>
+     * @param pattern key search pattern
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or empty map if no keys match the pattern
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMatchingCacheElements( String pattern )
+    {
+        return this.getCacheControl().getMatching( pattern );
+    }
+
+    /**
+     * Place a new object in the cache, associated with key name. If there is currently an object
+     * associated with name in the region an ObjectExistsException is thrown. Names are scoped to a
+     * region so they must be unique within the region they are placed.
+     * <p>
+     * @param key Key object will be stored with
+     * @param value Object to store
+     * @throws CacheException and ObjectExistsException is thrown if the item is already in the
+     *                cache.
+     */
+    @Override
+    public void putSafe( K key, V value )
+    {
+        if ( this.getCacheControl().get( key ) != null )
+        {
+            throw new ObjectExistsException( "putSafe failed.  Object exists in the cache for key [" + key
+                + "].  Remove first or use a non-safe put to override the value." );
+        }
+        put( key, value );
+    }
+
+    /**
+     * Place a new object in the cache, associated with key name. If there is currently an object
+     * associated with name in the region it is replaced. Names are scoped to a region so they must
+     * be unique within the region they are placed.
+     * @param name Key object will be stored with
+     * @param obj Object to store
+     */
+    @Override
+    public void put( K name, V obj )
+    {
+        // Call put with a copy of the contained caches default attributes.
+        // the attributes are copied by the cacheControl
+        put( name, obj, this.getCacheControl().getElementAttributes() );
+    }
+
+    /**
+     * Constructs a cache element with these attributes, and puts it into the cache.
+     * <p>
+     * If the key or the value is null, and InvalidArgumentException is thrown.
+     * <p>
+     * @see org.apache.commons.jcs3.access.behavior.ICacheAccess#put(Object, Object, IElementAttributes)
+     */
+    @Override
+    public void put( K key, V val, IElementAttributes attr )
+    {
+        if ( key == null )
+        {
+            throw new InvalidArgumentException( "Key must not be null" );
+        }
+
+        if ( val == null )
+        {
+            throw new InvalidArgumentException( "Value must not be null" );
+        }
+
+        // Create the element and update. This may throw an IOException which
+        // should be wrapped by cache access.
+        try
+        {
+            CacheElement<K, V> ce = new CacheElement<>( this.getCacheControl().getCacheName(), key,
+                                                val );
+
+            ce.setElementAttributes( attr );
+
+            this.getCacheControl().update( ce );
+        }
+        catch ( IOException e )
+        {
+            throw new CacheException( e );
+        }
+    }
+
+    /**
+     * Removes a single item by name.
+     * <p>
+     * @param name the name of the item to remove.
+     */
+    @Override
+    public void remove( K name )
+    {
+        this.getCacheControl().remove( name );
+    }
+
+    /**
+     * Reset attributes for a particular element in the cache. NOTE: this method is currently not
+     * implemented.
+     * <p>
+     * @param name Key of object to reset attributes for
+     * @param attr New attributes for the object
+     * @throws InvalidHandleException if the item does not exist.
+     */
+    @Override
+    public void resetElementAttributes( K name, IElementAttributes attr )
+    {
+        ICacheElement<K, V> element = this.getCacheControl().get( name );
+
+        if ( element == null )
+        {
+            throw new InvalidHandleException( "Object for name [" + name + "] is not in the cache" );
+        }
+
+        // Although it will work currently, don't assume pass by reference here,
+        // i.e. don't do this:
+        // element.setElementAttributes( attr );
+        // Another reason to call put is to force the changes to be distributed.
+
+        put( element.getKey(), element.getVal(), attr );
+    }
+
+    /**
+     * GetElementAttributes will return an attribute object describing the current attributes
+     * associated with the object name. The name object must override the Object.equals and
+     * Object.hashCode methods.
+     * <p>
+     * @param name Key of object to get attributes for
+     * @return Attributes for the object, null if object not in cache
+     */
+    @Override
+    public IElementAttributes getElementAttributes( K name ) throws CacheException
+    {
+        IElementAttributes attr = null;
+
+        try
+        {
+            attr = this.getCacheControl().getElementAttributes( name );
+        }
+        catch ( IOException ioe )
+        {
+            throw new CacheException("Failure getting element attributes", ioe);
+        }
+
+        return attr;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/GroupCacheAccess.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/GroupCacheAccess.java
new file mode 100644
index 0000000..cad9082
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/GroupCacheAccess.java
@@ -0,0 +1,206 @@
+package org.apache.commons.jcs3.access;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.commons.jcs3.access.behavior.IGroupCacheAccess;
+import org.apache.commons.jcs3.access.exception.CacheException;
+import org.apache.commons.jcs3.access.exception.InvalidArgumentException;
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+import org.apache.commons.jcs3.engine.control.group.GroupAttrName;
+import org.apache.commons.jcs3.engine.control.group.GroupId;
+
+/**
+ * Access for groups.
+ */
+public class GroupCacheAccess<K, V>
+    extends AbstractCacheAccess<GroupAttrName<K>, V>
+    implements IGroupCacheAccess<K, V>
+{
+    /**
+     * Constructor for the GroupCacheAccess object
+     * <p>
+     * @param cacheControl
+     */
+    public GroupCacheAccess( CompositeCache<GroupAttrName<K>, V> cacheControl )
+    {
+        super(cacheControl);
+    }
+
+    /**
+     * Gets an item out of the cache that is in a specified group.
+     * <p>
+     * @param name
+     *            The key name.
+     * @param group
+     *            The group name.
+     * @return The cached value, null if not found.
+     */
+    @Override
+    public V getFromGroup( K name, String group )
+    {
+        ICacheElement<GroupAttrName<K>, V> element = this.getCacheControl().get( getGroupAttrName( group, name ) );
+        return ( element != null ) ? element.getVal() : null;
+    }
+
+    /**
+     * Internal method used for group functionality.
+     * <p>
+     * @param group
+     * @param name
+     * @return GroupAttrName
+     */
+    private GroupAttrName<K> getGroupAttrName( String group, K name )
+    {
+        GroupId gid = new GroupId( this.getCacheControl().getCacheName(), group );
+        return new GroupAttrName<>( gid, name );
+    }
+
+    /**
+     * Allows the user to put an object into a group within a particular cache
+     * region. This method sets the object's attributes to the default for the
+     * region.
+     * <p>
+     * @param name
+     *            The key name.
+     * @param groupName
+     *            The group name.
+     * @param value
+     *            The object to cache
+     * @throws CacheException
+     */
+    @Override
+    public void putInGroup( K name, String groupName, V value )
+        throws CacheException
+    {
+        putInGroup( name, groupName, value, null );
+    }
+
+    /**
+     * Allows the user to put an object into a group within a particular cache
+     * region. This method allows the object's attributes to be individually
+     * specified.
+     * <p>
+     * @param name
+     *            The key name.
+     * @param groupName
+     *            The group name.
+     * @param value
+     *            The object to cache
+     * @param attr
+     *            The objects attributes.
+     * @throws CacheException
+     */
+    @Override
+    public void putInGroup( K name, String groupName, V value, IElementAttributes attr )
+        throws CacheException
+    {
+        if ( name == null )
+        {
+            throw new InvalidArgumentException( "Key must not be null" );
+        }
+
+        if ( value == null )
+        {
+            throw new InvalidArgumentException( "Value must not be null" );
+        }
+
+        // Create the element and update. This may throw an IOException which
+        // should be wrapped by cache access.
+        try
+        {
+            GroupAttrName<K> key = getGroupAttrName( groupName, name );
+            CacheElement<GroupAttrName<K>, V> ce =
+                new CacheElement<>( this.getCacheControl().getCacheName(), key, value );
+
+            IElementAttributes attributes = (attr == null) ? this.getCacheControl().getElementAttributes() : attr;
+            ce.setElementAttributes( attributes );
+
+            this.getCacheControl().update( ce );
+        }
+        catch ( IOException e )
+        {
+            throw new CacheException( e );
+        }
+
+    }
+
+    /**
+     * Removes a single item by name from a group.
+     *
+     * @param name
+     * @param group
+     */
+    @Override
+    public void removeFromGroup( K name, String group )
+    {
+        GroupAttrName<K> key = getGroupAttrName( group, name );
+        this.getCacheControl().remove( key );
+    }
+
+    /**
+     * Gets the set of keys of objects currently in the group.
+     * <p>
+     * @param group
+     * @return A Set of keys.
+     */
+    @Override
+    public Set<K> getGroupKeys( String group )
+    {
+        GroupId groupId = new GroupId( this.getCacheControl().getCacheName(), group );
+
+        return this.getCacheControl().getKeySet()
+                .stream()
+                .filter(gan -> gan.groupId.equals(groupId))
+                .map(gan -> gan.attrName)
+                .collect(Collectors.toSet());
+    }
+
+    /**
+     * Gets the set of group names in the cache
+     * <p>
+     * @return A Set of group names.
+     */
+    public Set<String> getGroupNames()
+    {
+        return this.getCacheControl().getKeySet()
+                .stream()
+                .map(gan -> gan.groupId.groupName)
+                .collect(Collectors.toSet());
+    }
+
+    /**
+     * Invalidates a group: remove all the group members
+     * <p>
+     * @param group
+     *            The name of the group to invalidate
+     */
+    @Override
+    public void invalidateGroup( String group )
+    {
+        this.getCacheControl().remove(getGroupAttrName(group, null));
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/behavior/ICacheAccess.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/behavior/ICacheAccess.java
new file mode 100644
index 0000000..5d1808e
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/behavior/ICacheAccess.java
@@ -0,0 +1,178 @@
+package org.apache.commons.jcs3.access.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Supplier;
+
+import org.apache.commons.jcs3.access.exception.CacheException;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+
+/**
+ * ICacheAccess defines the behavior for client access.
+ */
+public interface ICacheAccess<K, V>
+    extends ICacheAccessManagement
+{
+    /**
+     * Basic get method.
+     * <p>
+     * @param name
+     * @return Object or null if not found.
+     */
+    V get(K name);
+
+    /**
+     * Basic get method. If the object cannot be found in the cache, it will be
+     * retrieved by calling the supplier and subsequently storing it in the cache.
+     * <p>
+     * @param name
+     * @param supplier supplier to be called if the value is not found
+     * @return Object.
+     */
+    V get(K name, Supplier<V> supplier);
+
+    /**
+     * Retrieve matching objects from the cache region this instance provides access to.
+     * <p>
+     * @param pattern - a key pattern for the objects stored
+     * @return A map of key to values. These are stripped from the wrapper.
+     */
+    Map<K, V> getMatching(String pattern);
+
+    /**
+     * Puts in cache if an item does not exist with the name in that region.
+     * <p>
+     * @param name
+     * @param obj
+     * @throws CacheException
+     */
+    void putSafe(K name, V obj)
+        throws CacheException;
+
+    /**
+     * Puts and/or overrides an element with the name in that region.
+     * <p>
+     * @param name
+     * @param obj
+     * @throws CacheException
+     */
+    void put(K name, V obj)
+        throws CacheException;
+
+    /**
+     * Description of the Method
+     * <p>
+     * @param name
+     * @param obj
+     * @param attr
+     * @throws CacheException
+     */
+    void put(K name, V obj, IElementAttributes attr)
+        throws CacheException;
+
+    /**
+     * This method returns the ICacheElement&lt;K, V&gt; wrapper which provides access to element info and other
+     * attributes.
+     * <p>
+     * This returns a reference to the wrapper. Any modifications will be reflected in the cache. No
+     * defensive copy is made.
+     * <p>
+     * This method is most useful if you want to determine things such as the how long the element
+     * has been in the cache.
+     * <p>
+     * The last access time in the ElementAttributes should be current.
+     * <p>
+     * @param name Key the object is stored as
+     * @return The ICacheElement&lt;K, V&gt; if the object is found or null
+     */
+    ICacheElement<K, V> getCacheElement(K name);
+
+    /**
+     * Get multiple elements from the cache based on a set of cache keys.
+     * <p>
+     * This method returns the ICacheElement&lt;K, V&gt; wrapper which provides access to element info and other
+     * attributes.
+     * <p>
+     * This returns a reference to the wrapper. Any modifications will be reflected in the cache. No
+     * defensive copy is made.
+     * <p>
+     * This method is most useful if you want to determine things such as the how long the element
+     * has been in the cache.
+     * <p>
+     * The last access time in the ElementAttributes should be current.
+     * <p>
+     * @param names set of Object cache keys
+     * @return a map of Object key to ICacheElement&lt;K, V&gt; element, or empty map if none of the keys are
+     *         present
+     */
+    Map<K, ICacheElement<K, V>> getCacheElements(Set<K> names);
+
+    /**
+     * Get multiple elements from the cache based on a set of cache keys.
+     * <p>
+     * This method returns the ICacheElement&lt;K, V&gt; wrapper which provides access to element info and other
+     * attributes.
+     * <p>
+     * This returns a reference to the wrapper. Any modifications will be reflected in the cache. No
+     * defensive copy is made.
+     * <p>
+     * This method is most useful if you want to determine things such as the how long the element
+     * has been in the cache.
+     * <p>
+     * The last access time in the ElementAttributes should be current.
+     * <p>
+     * @param pattern key search pattern
+     * @return a map of Object key to ICacheElement&lt;K, V&gt; element, or empty map if no keys match the
+     *         pattern
+     */
+    Map<K, ICacheElement<K, V>> getMatchingCacheElements(String pattern);
+
+    /**
+     * Remove an object for this key if one exists, else do nothing.
+     * <p>
+     * @param name
+     * @throws CacheException
+     */
+    void remove(K name)
+        throws CacheException;
+
+    /**
+     * Reset the attributes on the object matching this key name.
+     * <p>
+     * @param name
+     * @param attributes
+     * @throws CacheException
+     */
+    void resetElementAttributes(K name, IElementAttributes attributes)
+        throws CacheException;
+
+    /**
+     * Gets the elementAttributes attribute of the ICacheAccess object
+     * <p>
+     * @param name
+     * @return The elementAttributes value
+     * @throws CacheException
+     */
+    IElementAttributes getElementAttributes(K name)
+        throws CacheException;
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/behavior/ICacheAccessManagement.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/behavior/ICacheAccessManagement.java
new file mode 100644
index 0000000..d3a9c18
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/behavior/ICacheAccessManagement.java
@@ -0,0 +1,111 @@
+package org.apache.commons.jcs3.access.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.access.exception.CacheException;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes;
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+import org.apache.commons.jcs3.engine.stats.behavior.ICacheStats;
+
+/**
+ * ICacheAccessManagement defines the methods for cache management, cleanup and shutdown.
+ */
+public interface ICacheAccessManagement
+{
+    /**
+     * Dispose this region. Flushes objects to and closes auxiliary caches. This is a shutdown
+     * command!
+     * <p>
+     * To simply remove all elements from the region use clear().
+     */
+    void dispose();
+
+    /**
+     * Removes all of the elements from a region.
+     * <p>
+     * @throws CacheException
+     */
+    void clear() throws CacheException;
+
+    /**
+     * GetElementAttributes will return an attribute object describing the current attributes
+     * associated with the object name. If no name parameter is available, the attributes for the
+     * region will be returned. The name object must override the Object.equals and Object.hashCode
+     * methods.
+     * <p>
+     * @return The elementAttributes value
+     * @throws CacheException
+     */
+    IElementAttributes getDefaultElementAttributes()
+        throws CacheException;
+
+    /**
+     * This method is does not reset the attributes for items already in the cache. It could
+     * potentially do this for items in memory, and maybe on disk (which would be slow) but not
+     * remote items. Rather than have unpredictable behavior, this method just sets the default
+     * attributes. Items subsequently put into the cache will use these defaults if they do not
+     * specify specific attributes.
+     * <p>
+     * @param attr the default attributes.
+     * @throws CacheException if something goes wrong.
+     */
+    void setDefaultElementAttributes( IElementAttributes attr ) throws CacheException;
+
+    /**
+     * Gets the ICompositeCacheAttributes of the cache region
+     * <p>
+     * @return ICompositeCacheAttributes
+     */
+    ICompositeCacheAttributes getCacheAttributes();
+
+    /**
+     * Sets the ICompositeCacheAttributes of the cache region
+     * <p>
+     * @param cattr The new ICompositeCacheAttribute value
+     */
+    void setCacheAttributes( ICompositeCacheAttributes cattr );
+
+    /**
+     * This instructs the memory cache to remove the <i>numberToFree</i> according to its eviction
+     * policy. For example, the LRUMemoryCache will remove the <i>numberToFree</i> least recently
+     * used items. These will be spooled to disk if a disk auxiliary is available.
+     * <p>
+     * @param numberToFree
+     * @return the number that were removed. if you ask to free 5, but there are only 3, you will
+     *         get 3.
+     * @throws CacheException
+     */
+    int freeMemoryElements( int numberToFree )
+        throws CacheException;
+
+    /**
+     * This returns the ICacheStats object with information on this region and its auxiliaries.
+     * <p>
+     * This data can be formatted as needed.
+     * <p>
+     * @return ICacheStats
+     */
+    ICacheStats getStatistics();
+
+    /**
+     * @return A String version of the stats.
+     */
+    String getStats();
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/behavior/IGroupCacheAccess.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/behavior/IGroupCacheAccess.java
new file mode 100644
index 0000000..ca09f8b
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/behavior/IGroupCacheAccess.java
@@ -0,0 +1,89 @@
+package org.apache.commons.jcs3.access.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Set;
+
+import org.apache.commons.jcs3.access.exception.CacheException;
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+
+/**
+ * IGroupCacheAccess defines group specific behavior for the client access
+ * classes.
+ */
+public interface IGroupCacheAccess<K, V>
+    extends ICacheAccessManagement
+{
+    /**
+     * Gets the g attribute of the IGroupCacheAccess object
+     * <p>
+     * @param name
+     * @param group
+     *            the name of the group to associate this with.
+     * @return The object that is keyed by the name in the group
+     */
+    V getFromGroup( K name, String group );
+
+    /**
+     * Puts an item in the cache associated with this group.
+     * <p>
+     * @param key
+     * @param group
+     * @param obj
+     * @throws CacheException
+     */
+    void putInGroup( K key, String group, V obj )
+        throws CacheException;
+
+    /**
+     * Put in the cache associated with this group using these attributes.
+     * <p>
+     * @param key
+     * @param group
+     * @param obj
+     * @param attr
+     * @throws CacheException
+     */
+    void putInGroup( K key, String group, V obj, IElementAttributes attr )
+        throws CacheException;
+
+    /**
+     * Remove the item from this group in this region by this name.
+     * <p>
+     * @param name
+     * @param group
+     */
+    void removeFromGroup( K name, String group );
+
+    /**
+     * Gets the set of keys of objects currently in the group
+     * <p>
+     * @param group
+     * @return the set of group keys.
+     */
+    Set<K> getGroupKeys( String group );
+
+    /**
+     * Invalidates a group
+     * <p>
+     * @param group
+     */
+    void invalidateGroup( String group );
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/exception/CacheException.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/exception/CacheException.java
new file mode 100644
index 0000000..91e5a72
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/exception/CacheException.java
@@ -0,0 +1,66 @@
+package org.apache.commons.jcs3.access.exception;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * This is the most general exception the cache throws.
+ */
+public class CacheException
+    extends RuntimeException
+{
+    /** Don't change. */
+    private static final long serialVersionUID = 8725795372935590265L;
+
+    /**
+     * Default
+     */
+    public CacheException()
+    {
+        super();
+    }
+
+    /**
+     * Constructor for the CacheException object
+     * @param nested a nested exception
+     */
+    public CacheException( Throwable nested )
+    {
+        super(nested);
+    }
+
+    /**
+     * Constructor for the CacheException object
+     * @param message the exception message
+     */
+    public CacheException( String message )
+    {
+        super(message);
+    }
+
+    /**
+     * Constructor for the CacheException object
+     * @param message the exception message
+     * @param nested a nested exception
+     */
+    public CacheException(String message, Throwable nested)
+    {
+        super(message, nested);
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/exception/ConfigurationException.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/exception/ConfigurationException.java
new file mode 100644
index 0000000..add6932
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/exception/ConfigurationException.java
@@ -0,0 +1,44 @@
+package org.apache.commons.jcs3.access.exception;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/** Thrown if there is some severe configuration problem that makes the cache nonfunctional. */
+public class ConfigurationException
+    extends CacheException
+{
+    /** Don't change. */
+    private static final long serialVersionUID = 6881044536186097055L;
+
+    /** Constructor for the ConfigurationException object */
+    public ConfigurationException()
+    {
+        super();
+    }
+
+    /**
+     * Constructor for the ConfigurationException object.
+     * <p>
+     * @param message
+     */
+    public ConfigurationException( String message )
+    {
+        super( message );
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/exception/InvalidArgumentException.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/exception/InvalidArgumentException.java
new file mode 100644
index 0000000..e3f3d00
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/exception/InvalidArgumentException.java
@@ -0,0 +1,47 @@
+package org.apache.commons.jcs3.access.exception;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * InvalidArgumentException is thrown if an argument is passed to the cache that is invalid. For
+ * instance, null values passed to put result in this exception.
+ */
+public class InvalidArgumentException
+    extends CacheException
+{
+    /** Don't change. */
+    private static final long serialVersionUID = -6058373692208755562L;
+
+    /** Constructor for the InvalidArgumentException object */
+    public InvalidArgumentException()
+    {
+        super();
+    }
+
+    /**
+     * Constructor for the InvalidArgumentException object.
+     * <p>
+     * @param message
+     */
+    public InvalidArgumentException( String message )
+    {
+        super( message );
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/exception/InvalidGroupException.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/exception/InvalidGroupException.java
new file mode 100644
index 0000000..2e50513
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/exception/InvalidGroupException.java
@@ -0,0 +1,47 @@
+package org.apache.commons.jcs3.access.exception;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * InvalidGroupException
+ */
+public class InvalidGroupException
+    extends CacheException
+{
+    /** Don't change. */
+    private static final long serialVersionUID = -5219807114008843480L;
+
+    /** Constructor for the InvalidGroupException object */
+    public InvalidGroupException()
+    {
+        super();
+    }
+
+    /**
+     * Constructor for the InvalidGroupException object
+     * <p>
+     * @param message
+     */
+    public InvalidGroupException( String message )
+    {
+        super( message );
+    }
+
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/exception/InvalidHandleException.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/exception/InvalidHandleException.java
new file mode 100644
index 0000000..b16c828
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/exception/InvalidHandleException.java
@@ -0,0 +1,48 @@
+package org.apache.commons.jcs3.access.exception;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * InvalidHandleException is not used.
+ */
+public class InvalidHandleException
+    extends CacheException
+{
+    /** Don't change. */
+    private static final long serialVersionUID = -5947822454839845924L;
+
+    /** Constructor for the InvalidHandleException object */
+    public InvalidHandleException()
+    {
+        // nothing
+        super();
+    }
+
+    /**
+     * Constructor for the InvalidHandleException object.
+     * <p>
+     * @param message
+     */
+    public InvalidHandleException( String message )
+    {
+        super( message );
+    }
+
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/exception/ObjectExistsException.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/exception/ObjectExistsException.java
new file mode 100644
index 0000000..10207e0
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/exception/ObjectExistsException.java
@@ -0,0 +1,53 @@
+package org.apache.commons.jcs3.access.exception;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * The putSafe method on the JCS convenience class throws this exception if the object is already
+ * present in the cache.
+ * <p>
+ * I'm removing this exception from normal use.
+ * <p>
+ * The overhead of throwing exceptions and the cumbersomeness of coding around exceptions warrants
+ * removal. Exceptions like this don't make sense to throw in the course of normal operations to
+ * signify a normal and expected condition. Returning null if an object isn't found is sufficient.
+ */
+public class ObjectExistsException
+    extends CacheException
+{
+    /** Don't change. */
+    private static final long serialVersionUID = -3779745827993383872L;
+
+    /** Constructor for the ObjectExistsException object */
+    public ObjectExistsException()
+    {
+        super();
+    }
+
+    /**
+     * Constructor for the ObjectExistsException object
+     * @param message
+     */
+    public ObjectExistsException( String message )
+    {
+        super( message );
+    }
+
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/exception/ObjectNotFoundException.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/exception/ObjectNotFoundException.java
new file mode 100644
index 0000000..b234efe
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/exception/ObjectNotFoundException.java
@@ -0,0 +1,51 @@
+package org.apache.commons.jcs3.access.exception;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * ObjectNotFoundException
+ * <p>
+ * TODO see if we can remove this.
+ * <p>
+ * This is thrown from the composite cache if you as for the element attributes and the element does
+ * not exist.
+ */
+public class ObjectNotFoundException
+    extends CacheException
+{
+    /** Don't change. */
+    private static final long serialVersionUID = 5684353421076546842L;
+
+    /** Constructor for the ObjectNotFoundException object */
+    public ObjectNotFoundException()
+    {
+        super();
+    }
+
+    /**
+     * Constructor for the ObjectNotFoundException object
+     * @param message
+     */
+    public ObjectNotFoundException( String message )
+    {
+        super( message );
+    }
+
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/access/package.html b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/package.html
similarity index 100%
rename from commons-jcs-core/src/main/java/org/apache/commons/jcs/access/package.html
rename to commons-jcs-core/src/main/java/org/apache/commons/jcs3/access/package.html
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/admin/CacheElementInfo.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/admin/CacheElementInfo.java
new file mode 100644
index 0000000..88d3813
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/admin/CacheElementInfo.java
@@ -0,0 +1,124 @@
+package org.apache.commons.jcs3.admin;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.beans.ConstructorProperties;
+
+
+/**
+ * Stores info on a cache element for the template
+ */
+public class CacheElementInfo
+{
+    /** element key */
+    private final String key;
+
+    /** is it eternal */
+    private final boolean eternal;
+
+    /** when it was created */
+    private final String createTime;
+
+    /** max life */
+    private final long maxLifeSeconds;
+
+    /** when it will expire */
+    private final long expiresInSeconds;
+
+    /**
+     * Parameterized constructor
+     *
+	 * @param key element key
+	 * @param eternal is it eternal
+	 * @param createTime when it was created
+	 * @param maxLifeSeconds max life
+	 * @param expiresInSeconds when it will expire
+	 */
+    @ConstructorProperties({"key", "eternal", "createTime", "maxLifeSeconds", "expiresInSeconds"})
+    public CacheElementInfo(String key, boolean eternal, String createTime,
+			long maxLifeSeconds, long expiresInSeconds)
+    {
+		super();
+		this.key = key;
+		this.eternal = eternal;
+		this.createTime = createTime;
+		this.maxLifeSeconds = maxLifeSeconds;
+		this.expiresInSeconds = expiresInSeconds;
+	}
+
+	/**
+     * @return a string representation of the key
+     */
+    public String getKey()
+    {
+        return this.key;
+    }
+
+    /**
+     * @return true if the item does not expire
+     */
+    public boolean isEternal()
+    {
+        return this.eternal;
+    }
+
+    /**
+     * @return the time the object was created
+     */
+    public String getCreateTime()
+    {
+        return this.createTime;
+    }
+
+    /**
+     * Ignored if isEternal
+     * @return the longest this object can live.
+     */
+    public long getMaxLifeSeconds()
+    {
+        return this.maxLifeSeconds;
+    }
+
+    /**
+     * Ignored if isEternal
+     * @return how many seconds until this object expires.
+     */
+    public long getExpiresInSeconds()
+    {
+        return this.expiresInSeconds;
+    }
+
+    /**
+     * @return string info on the item
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        buf.append( "\nCacheElementInfo " );
+        buf.append( "\n Key [" ).append( getKey() ).append( "]" );
+        buf.append( "\n Eternal [" ).append( isEternal() ).append( "]" );
+        buf.append( "\n CreateTime [" ).append( getCreateTime() ).append( "]" );
+        buf.append( "\n MaxLifeSeconds [" ).append( getMaxLifeSeconds() ).append( "]" );
+        buf.append( "\n ExpiresInSeconds [" ).append( getExpiresInSeconds() ).append( "]" );
+
+        return buf.toString();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/admin/CacheRegionInfo.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/admin/CacheRegionInfo.java
new file mode 100644
index 0000000..11dfce5
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/admin/CacheRegionInfo.java
@@ -0,0 +1,180 @@
+package org.apache.commons.jcs3.admin;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.beans.ConstructorProperties;
+
+
+
+/**
+ * Stores info on a cache region for the template
+ */
+public class CacheRegionInfo
+{
+    /** The name of the cache region */
+    private final String cacheName;
+
+    /** The size of the cache region */
+    private final int cacheSize;
+
+    /** The status of the cache region */
+    private final String cacheStatus;
+
+    /** The statistics of the cache region */
+    private final String cacheStatistics;
+
+    /** The number of memory hits in the cache region */
+    private final long hitCountRam;
+
+    /** The number of auxiliary hits in the cache region */
+    private final long hitCountAux;
+
+    /** The number of misses in the cache region because the items were not found */
+    private final long missCountNotFound;
+
+    /** The number of misses in the cache region because the items were expired */
+    private final long missCountExpired;
+
+    /** The number of bytes counted so far, will be a total of all items */
+    private final long byteCount;
+
+    /**
+     * Parameterized constructor
+     *
+	 * @param cacheName The name of the cache region
+	 * @param cacheSize The size of the cache region
+	 * @param cacheStatus The status of the cache region
+	 * @param cacheStatistics The statistics of the cache region
+	 * @param hitCountRam The number of memory hits in the cache region
+	 * @param hitCountAux The number of auxiliary hits in the cache region
+	 * @param missCountNotFound The number of misses in the cache region because the items were not found
+	 * @param missCountExpired The number of misses in the cache region because the items were expired
+	 * @param byteCount The number of bytes counted so far, will be a total of all items
+	 */
+    @ConstructorProperties({"cacheName", "cacheSize", "cacheStatus", "cacheStatistics",
+    	"hitCountRam", "hitCountAux", "missCountNotFound", "missCountExpired", "byteCount"})
+	public CacheRegionInfo(String cacheName, int cacheSize, String cacheStatus,
+			String cacheStatistics, long hitCountRam, long hitCountAux,
+			long missCountNotFound, long missCountExpired, long byteCount)
+	{
+		super();
+		this.cacheName = cacheName;
+		this.cacheSize = cacheSize;
+		this.cacheStatus = cacheStatus;
+		this.cacheStatistics = cacheStatistics;
+		this.hitCountRam = hitCountRam;
+		this.hitCountAux = hitCountAux;
+		this.missCountNotFound = missCountNotFound;
+		this.missCountExpired = missCountExpired;
+		this.byteCount = byteCount;
+	}
+
+	/**
+	 * @return the cacheName
+	 */
+	public String getCacheName()
+	{
+		return this.cacheName;
+	}
+
+	/**
+	 * @return the cacheSize
+	 */
+	public int getCacheSize()
+	{
+		return this.cacheSize;
+	}
+
+	/**
+     * @return a status string
+     */
+    public String getCacheStatus()
+    {
+        return this.cacheStatus;
+    }
+
+    /**
+     * Return the statistics for the region.
+     * <p>
+     * @return String
+     */
+    public String getCacheStatistics()
+    {
+        return this.cacheStatistics;
+    }
+
+    /**
+	 * @return the hitCountRam
+	 */
+	public long getHitCountRam()
+	{
+		return hitCountRam;
+	}
+
+	/**
+	 * @return the hitCountAux
+	 */
+	public long getHitCountAux()
+	{
+		return hitCountAux;
+	}
+
+	/**
+	 * @return the missCountNotFound
+	 */
+	public long getMissCountNotFound()
+	{
+		return missCountNotFound;
+	}
+
+	/**
+	 * @return the missCountExpired
+	 */
+	public long getMissCountExpired()
+	{
+		return missCountExpired;
+	}
+
+	/**
+     * @return total byte count
+     */
+    public long getByteCount()
+    {
+        return this.byteCount;
+    }
+
+    /**
+     * @return string info on the region
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        buf.append( "\nCacheRegionInfo " );
+        if ( cacheName != null )
+        {
+            buf.append( "\n CacheName [" + cacheName + "]" );
+            buf.append( "\n Status [" + cacheStatus + "]" );
+        }
+        buf.append( "\n ByteCount [" + getByteCount() + "]" );
+
+        return buf.toString();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/admin/CountingOnlyOutputStream.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/admin/CountingOnlyOutputStream.java
new file mode 100644
index 0000000..3ee9736
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/admin/CountingOnlyOutputStream.java
@@ -0,0 +1,84 @@
+package org.apache.commons.jcs3.admin;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Keeps track of the number of bytes written to it, but doesn't write them anywhere.
+ */
+public class CountingOnlyOutputStream
+    extends OutputStream
+{
+    /** number of bytes passed through */
+    private int count; // TODO should this be long?
+
+    /**
+     * count as we write.
+     * <p>
+     * @param b
+     * @throws IOException
+     */
+    @Override
+    public void write( byte[] b )
+        throws IOException
+    {
+        this.count += b.length;
+    }
+
+    /**
+     * count as we write.
+     * <p>
+     * @param b
+     * @param off
+     * @param len
+     * @throws IOException
+     */
+    @Override
+    public void write( byte[] b, int off, int len )
+        throws IOException
+    {
+        this.count += len;
+    }
+
+    /**
+     * count as we write.
+     * <p>
+     * @param b
+     * @throws IOException
+     */
+    @Override
+    public void write( int b )
+        throws IOException
+    {
+        this.count++;
+    }
+
+    /**
+     * The number of bytes that have passed through this stream.
+     * <p>
+     * @return int
+     */
+    public int getCount()
+    {
+        return this.count;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/admin/JCSAdmin.jsp b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/admin/JCSAdmin.jsp
new file mode 100644
index 0000000..b01bef0
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/admin/JCSAdmin.jsp
@@ -0,0 +1,310 @@
+<%--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+--%>
+<%@page import="org.apache.commons.jcs3.JCS"%>
+<%@page import="org.apache.commons.jcs3.access.CacheAccess" %>
+<%@page import="org.apache.commons.jcs3.admin.CacheElementInfo" %>
+<%@page import="org.apache.commons.jcs3.admin.CacheRegionInfo" %>
+<%@page import="java.io.Serializable" %>
+<%@page import="java.util.HashMap" %>
+
+<jsp:useBean id="jcsBean" scope="request" class="org.apache.commons.jcs3.admin.JCSAdminBean" />
+
+<html>
+<head>
+
+<SCRIPT LANGUAGE="Javascript">
+  function decision( message, url )
+  {
+    if( confirm(message) )
+    {
+      location.href = url;
+    }
+  }
+</SCRIPT>
+
+<title> JCS Admin Servlet </title>
+
+</head>
+
+<body>
+
+<%
+			String CACHE_NAME_PARAM = "cacheName";
+			String ACTION_PARAM = "action";
+		 	String CLEAR_ALL_REGIONS_ACTION = "clearAllRegions";
+		 	String CLEAR_REGION_ACTION = "clearRegion";
+		 	String REMOVE_ACTION = "remove";
+		 	String DETAIL_ACTION = "detail";
+		 	String REGION_SUMMARY_ACTION = "regionSummary";
+		 	String ITEM_ACTION = "item";
+			String KEY_PARAM = "key";
+			String SILENT_PARAM = "silent";
+
+     		String DEFAULT_TEMPLATE_NAME = "DEFAULT";
+     		String REGION_DETAIL_TEMPLATE_NAME = "DETAIL";
+     		String ITEM_TEMPLATE_NAME = "ITEM";
+     		String REGION_SUMMARY_TEMPLATE_NAME = "SUMMARY";
+
+			String templateName = DEFAULT_TEMPLATE_NAME;
+
+			HashMap<String, Object> context = new HashMap<String, Object>();
+
+			// Get cacheName for actions from request (might be null)
+			String cacheName = request.getParameter( CACHE_NAME_PARAM );
+
+			if ( cacheName != null )
+			{
+			    cacheName = cacheName.trim();
+			}
+
+			// If an action was provided, handle it
+			String action = request.getParameter( ACTION_PARAM );
+
+			if ( action != null )
+			{
+				if ( action.equals( CLEAR_ALL_REGIONS_ACTION ) )
+				{
+					jcsBean.clearAllRegions();
+				}
+				else if ( action.equals( CLEAR_REGION_ACTION ) )
+				{
+					if ( cacheName == null )
+					{
+						// Not Allowed
+					}
+					else
+					{
+						jcsBean.clearRegion( cacheName );
+					}
+				}
+				else if ( action.equals( REMOVE_ACTION ) )
+				{
+					String[] keys = request.getParameterValues( KEY_PARAM );
+
+					for ( int i = 0; i < keys.length; i++ )
+					{
+						jcsBean.removeItem( cacheName, keys[ i ] );
+					}
+
+					templateName = REGION_DETAIL_TEMPLATE_NAME;
+				}
+				else if ( action.equals( DETAIL_ACTION ) )
+				{
+					templateName = REGION_DETAIL_TEMPLATE_NAME;
+				}
+				else if ( action.equals( ITEM_ACTION ) )
+				{
+					templateName = ITEM_TEMPLATE_NAME;
+				}
+				else if ( action.equals( REGION_SUMMARY_ACTION ) )
+				{
+					templateName = REGION_SUMMARY_TEMPLATE_NAME;
+				}
+			}
+
+			if ( request.getParameter( SILENT_PARAM ) != null )
+			{
+				// If silent parameter was passed, no output should be produced.
+				//return null;
+			}
+			else
+			{
+				// Populate the context based on the template
+				if ( templateName == REGION_DETAIL_TEMPLATE_NAME )
+				{
+					//context.put( "cacheName", cacheName );
+					context.put( "elementInfoRecords", jcsBean.buildElementInfo( cacheName ) );
+				}
+				else if ( templateName == DEFAULT_TEMPLATE_NAME )
+				{
+					context.put( "cacheInfoRecords", jcsBean.buildCacheInfo() );
+				}
+			}
+
+///////////////////////////////////////////////////////////////////////////////////
+			//handle display
+
+			if ( templateName == ITEM_TEMPLATE_NAME )
+			{
+			    String key = request.getParameter( KEY_PARAM );
+
+			    if ( key != null )
+			    {
+			        key = key.trim();
+			    }
+
+			    CacheAccess<Serializable, Serializable> cache = JCS.getInstance( cacheName );
+				org.apache.commons.jcs3.engine.behavior.ICacheElement<?, ?> element = cache.getCacheElement( key );
+%>
+<h1> Item for key [<%=key%>] in region [<%=cacheName%>] </h1>
+
+<a href="JCSAdmin.jsp?action=detail&cacheName=<%=cacheName%>">Region Detail</a>
+| <a href="JCSAdmin.jsp">All Regions</a>
+
+  <pre>
+	<%=element%>
+  </pre>
+<%
+			}
+			else if ( templateName == REGION_SUMMARY_TEMPLATE_NAME )
+			{
+%>
+
+<h1> Summary for region [<%=cacheName%>] </h1>
+
+<a href="JCSAdmin.jsp">All Regions</a>
+
+<%
+    CacheAccess<?, ?> cache = JCS.getInstance( cacheName );
+    String stats = cache.getStats();
+%>
+
+    <br>
+<b> Stats for region [<%=cacheName%>] </b>
+
+    <pre>
+    	<%=stats%>
+    </pre>
+
+<%
+			}
+			else if ( templateName == REGION_DETAIL_TEMPLATE_NAME )
+			{
+%>
+
+<h1> Detail for region [<%=cacheName%>] </h1>
+
+<a href="JCSAdmin.jsp">All Regions</a>
+
+<table border="1" cellpadding="5" >
+    <tr>
+        <th> Key </th>
+        <th> Eternal? </th>
+        <th> Create time </th>
+        <th> Max Life (s) </th>
+        <th> Till Expiration (s) </th>
+    </tr>
+<%
+	CacheElementInfo[] list = (CacheElementInfo[]) context.get( "elementInfoRecords" );
+    for (CacheElementInfo element : list)
+    {
+%>
+        <tr>
+            <td> <%=element.getKey()%> </td>
+            <td> <%=element.isEternal()%> </td>
+            <td> <%=element.getCreateTime()%> </td>
+            <td> <%=element.getMaxLifeSeconds()%> </td>
+            <td> <%=element.getExpiresInSeconds()%> </td>
+            <td>
+             <a href="JCSAdmin.jsp?action=item&cacheName=<%=cacheName%>&key=<%=element.getKey()%>"> View </a>
+            | <a href="JCSAdmin.jsp?action=remove&cacheName=<%=cacheName%>&key=<%=element.getKey()%>"> Remove </a>
+            </td>
+        </tr>
+<%
+    }
+
+    CacheAccess<?, ?> cache = JCS.getInstance( cacheName );
+    String stats = cache.getStats();
+%>
+    </table>
+
+    <br>
+<b> Stats for region [<%=cacheName%>] </b>
+
+    <pre>
+    	<%=stats%>
+    </pre>
+<%
+  }
+  else
+  {
+%>
+
+<h1> Cache Regions </h1>
+
+<p>
+These are the regions which are currently defined in the cache. 'Items' and
+'Bytes' refer to the elements currently in memory (not spooled). You can clear
+all items for a region by selecting 'Remove all' next to the desired region
+below. You can also <a href="javascript:decision('Clicking OK will clear all the data from all regions!','JCSAdmin.jsp?action=clearAllRegions')">Clear all regions</a>
+which empties the entire cache.
+</p>
+<p>
+	<form action="JCSAdmin.jsp">
+		<input type="hidden" name="action" value="item">
+		Retrieve (key) <input type="text" name="key"> &nbsp;
+		(region) <select name="cacheName">
+<%
+  CacheRegionInfo[] listSelect = (CacheRegionInfo[]) context.get( "cacheInfoRecords" );
+  for (CacheRegionInfo record : listSelect)
+  {
+	%>
+    <option value="<%=record.getCacheName()%>"><%=record.getCacheName()%></option>
+	<%
+  }
+%>
+				</select>
+		<input type="submit">
+	</form>
+</p>
+
+<table border="1" cellpadding="5" >
+    <tr>
+        <th> Cache Name </th>
+        <th> Items </th>
+        <th> Bytes </th>
+        <th> Status </th>
+        <th> Memory Hits </th>
+        <th> Aux Hits </th>
+        <th> Not Found Misses </th>
+        <th> Expired Misses </th>
+    </tr>
+
+<%
+	CacheRegionInfo[] list = (CacheRegionInfo[]) context.get( "cacheInfoRecords" );
+    for (CacheRegionInfo record : listSelect)
+    {
+%>
+        <tr>
+            <td> <%=record.getCacheName()%> </td>
+            <td> <%=record.getCacheSize()%> </td>
+            <td> <%=record.getByteCount()%> </td>
+            <td> <%=record.getCacheStatus()%> </td>
+            <td> <%=record.getHitCountRam()%> </td>
+            <td> <%=record.getHitCountAux()%> </td>
+            <td> <%=record.getMissCountNotFound()%> </td>
+            <td> <%=record.getMissCountExpired()%> </td>
+            <td>
+                <a href="JCSAdmin.jsp?action=regionSummary&cacheName=<%=record.getCacheName()%>"> Summary </a>
+                | <a href="JCSAdmin.jsp?action=detail&cacheName=<%=record.getCacheName()%>"> Detail </a>
+                | <a href="javascript:decision('Clicking OK will remove all the data from the region [<%=record.getCacheName()%>]!','JCSAdmin.jsp?action=clearRegion&cacheName=<%=record.getCacheName()%>')"> Clear </a>
+            </td>
+        </tr>
+<%
+    }
+%>
+    </table>
+<%
+  }
+%>
+
+
+</body>
+
+</html>
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/admin/JCSAdminBean.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/admin/JCSAdminBean.java
new file mode 100644
index 0000000..d1468a7
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/admin/JCSAdminBean.java
@@ -0,0 +1,391 @@
+package org.apache.commons.jcs3.admin;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.text.DateFormat;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+
+import org.apache.commons.jcs3.access.exception.CacheException;
+import org.apache.commons.jcs3.auxiliary.remote.server.RemoteCacheServer;
+import org.apache.commons.jcs3.auxiliary.remote.server.RemoteCacheServerFactory;
+import org.apache.commons.jcs3.engine.CacheElementSerialized;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+import org.apache.commons.jcs3.engine.control.CompositeCacheManager;
+import org.apache.commons.jcs3.engine.memory.behavior.IMemoryCache;
+
+/**
+ * A servlet which provides HTTP access to JCS. Allows a summary of regions to be viewed, and
+ * removeAll to be run on individual regions or all regions. Also provides the ability to remove
+ * items (any number of key arguments can be provided with action 'remove'). Should be initialized
+ * with a properties file that provides at least a classpath resource loader.
+ */
+public class JCSAdminBean implements JCSJMXBean
+{
+    /** The cache manager. */
+    private final CompositeCacheManager cacheHub;
+
+    /**
+     * Default constructor
+     */
+    public JCSAdminBean()
+    {
+        super();
+        try
+        {
+            this.cacheHub = CompositeCacheManager.getInstance();
+        }
+        catch (CacheException e)
+        {
+            throw new RuntimeException("Could not retrieve cache manager instance", e);
+        }
+    }
+
+    /**
+     * Parameterized constructor
+     *
+	 * @param cacheHub the cache manager instance
+	 */
+	public JCSAdminBean(CompositeCacheManager cacheHub)
+	{
+		super();
+		this.cacheHub = cacheHub;
+	}
+
+	/**
+     * Builds up info about each element in a region.
+     * <p>
+     * @param cacheName
+     * @return List of CacheElementInfo objects
+     * @throws IOException
+     */
+    @Override
+    public List<CacheElementInfo> buildElementInfo( String cacheName )
+        throws IOException
+    {
+        CompositeCache<Object, Object> cache = cacheHub.getCache( cacheName );
+
+        // Convert all keys to string, store in a sorted map
+        TreeMap<String, ?> keys = new TreeMap<>(cache.getMemoryCache().getKeySet()
+                .stream()
+                .collect(Collectors.toMap(k -> k.toString(), k -> k)));
+
+        LinkedList<CacheElementInfo> records = new LinkedList<>();
+
+        DateFormat format = DateFormat.getDateTimeInstance( DateFormat.SHORT, DateFormat.SHORT );
+
+        long now = System.currentTimeMillis();
+
+        for (Map.Entry<String, ?> key : keys.entrySet())
+        {
+            ICacheElement<?, ?> element = cache.getMemoryCache().getQuiet( key.getValue() );
+
+            IElementAttributes attributes = element.getElementAttributes();
+
+            CacheElementInfo elementInfo = new CacheElementInfo(
+            		key.getKey(),
+            		attributes.getIsEternal(),
+            		format.format(new Date(attributes.getCreateTime())),
+            		attributes.getMaxLife(),
+            		(now - attributes.getCreateTime() - attributes.getMaxLife() * 1000 ) / -1000);
+
+            records.add( elementInfo );
+        }
+
+        return records;
+    }
+
+    /**
+     * Builds up data on every region.
+     * <p>
+     * TODO we need a most light weight method that does not count bytes. The byte counting can
+     *       really swamp a server.
+     * @return List of CacheRegionInfo objects
+     */
+    @Override
+    public List<CacheRegionInfo> buildCacheInfo()
+    {
+        TreeSet<String> cacheNames = new TreeSet<>(cacheHub.getCacheNames());
+
+        LinkedList<CacheRegionInfo> cacheInfo = new LinkedList<>();
+
+        for (String cacheName : cacheNames)
+        {
+            CompositeCache<?, ?> cache = cacheHub.getCache( cacheName );
+
+            CacheRegionInfo regionInfo = new CacheRegionInfo(
+                    cache.getCacheName(),
+                    cache.getSize(),
+                    cache.getStatus().toString(),
+                    cache.getStats(),
+                    cache.getHitCountRam(),
+                    cache.getHitCountAux(),
+                    cache.getMissCountNotFound(),
+                    cache.getMissCountExpired(),
+                    getByteCount( cache ));
+
+            cacheInfo.add( regionInfo );
+        }
+
+        return cacheInfo;
+    }
+
+
+	/**
+     * Tries to estimate how much data is in a region. This is expensive. If there are any non serializable objects in
+     * the region or an error occurs, suppresses exceptions and returns 0.
+     * <p>
+     *
+     * @return int The size of the region in bytes.
+     */
+	@Override
+    public long getByteCount(String cacheName)
+	{
+		return getByteCount(cacheHub.getCache(cacheName));
+	}
+
+	/**
+     * Tries to estimate how much data is in a region. This is expensive. If there are any non serializable objects in
+     * the region or an error occurs, suppresses exceptions and returns 0.
+     * <p>
+     *
+     * @return int The size of the region in bytes.
+     */
+    public <K, V> long getByteCount(CompositeCache<K, V> cache)
+    {
+        if (cache == null)
+        {
+            throw new IllegalArgumentException("The cache object specified was null.");
+        }
+
+        long size = 0;
+        IMemoryCache<K, V> memCache = cache.getMemoryCache();
+
+        for (K key : memCache.getKeySet())
+        {
+            ICacheElement<K, V> ice = null;
+			try
+			{
+				ice = memCache.get(key);
+			}
+			catch (IOException e)
+			{
+                throw new RuntimeException("IOException while trying to get a cached element", e);
+			}
+
+			if (ice == null)
+			{
+				continue;
+			}
+
+			if (ice instanceof CacheElementSerialized)
+            {
+                size += ((CacheElementSerialized<K, V>) ice).getSerializedValue().length;
+            }
+            else
+            {
+                Object element = ice.getVal();
+
+                //CountingOnlyOutputStream: Keeps track of the number of bytes written to it, but doesn't write them anywhere.
+                CountingOnlyOutputStream counter = new CountingOnlyOutputStream();
+                try (ObjectOutputStream out = new ObjectOutputStream(counter);)
+                {
+                    out.writeObject(element);
+                }
+                catch (IOException e)
+                {
+                    throw new RuntimeException("IOException while trying to measure the size of the cached element", e);
+                }
+                finally
+                {
+                	try
+                	{
+						counter.close();
+					}
+                	catch (IOException e)
+                	{
+                		// ignore
+					}
+                }
+
+                // 4 bytes lost for the serialization header
+                size += counter.getCount() - 4;
+            }
+        }
+
+        return size;
+    }
+
+    /**
+     * Clears all regions in the cache.
+     * <p>
+     * If this class is running within a remote cache server, clears all regions via the <code>RemoteCacheServer</code>
+     * API, so that removes will be broadcast to client machines. Otherwise clears all regions in the cache directly via
+     * the usual cache API.
+     */
+    @Override
+    public void clearAllRegions() throws IOException
+    {
+        RemoteCacheServer<?, ?> remoteCacheServer = RemoteCacheServerFactory.getRemoteCacheServer();
+
+        if (remoteCacheServer == null)
+        {
+            // Not running in a remote cache server.
+            // Remove objects from the cache directly, as no need to broadcast removes to client machines...
+            for (String name : cacheHub.getCacheNames())
+            {
+                cacheHub.getCache(name).removeAll();
+            }
+        }
+        else
+        {
+            // Running in a remote cache server.
+            // Remove objects via the RemoteCacheServer API, so that removes will be broadcast to client machines...
+            // Call remoteCacheServer.removeAll(String) for each cacheName...
+            for (String name : cacheHub.getCacheNames())
+            {
+                remoteCacheServer.removeAll(name);
+            }
+        }
+    }
+
+    /**
+     * Clears a particular cache region.
+     * <p>
+     * If this class is running within a remote cache server, clears the region via the <code>RemoteCacheServer</code>
+     * API, so that removes will be broadcast to client machines. Otherwise clears the region directly via the usual
+     * cache API.
+     */
+    @Override
+    public void clearRegion(String cacheName) throws IOException
+    {
+        if (cacheName == null)
+        {
+            throw new IllegalArgumentException("The cache name specified was null.");
+        }
+        if (RemoteCacheServerFactory.getRemoteCacheServer() == null)
+        {
+            // Not running in a remote cache server.
+            // Remove objects from the cache directly, as no need to broadcast removes to client machines...
+            cacheHub.getCache(cacheName).removeAll();
+        }
+        else
+        {
+            // Running in a remote cache server.
+            // Remove objects via the RemoteCacheServer API, so that removes will be broadcast to client machines...
+            try
+            {
+                // Call remoteCacheServer.removeAll(String)...
+                RemoteCacheServer<?, ?> remoteCacheServer = RemoteCacheServerFactory.getRemoteCacheServer();
+                remoteCacheServer.removeAll(cacheName);
+            }
+            catch (IOException e)
+            {
+                throw new IllegalStateException("Failed to remove all elements from cache region [" + cacheName + "]: " + e, e);
+            }
+        }
+    }
+
+    /**
+     * Removes a particular item from a particular region.
+     * <p>
+     * If this class is running within a remote cache server, removes the item via the <code>RemoteCacheServer</code>
+     * API, so that removes will be broadcast to client machines. Otherwise clears the region directly via the usual
+     * cache API.
+     *
+     * @param cacheName
+     * @param key
+     *
+     * @throws IOException
+     */
+    @Override
+    public void removeItem(String cacheName, String key) throws IOException
+    {
+        if (cacheName == null)
+        {
+            throw new IllegalArgumentException("The cache name specified was null.");
+        }
+        if (key == null)
+        {
+            throw new IllegalArgumentException("The key specified was null.");
+        }
+        if (RemoteCacheServerFactory.getRemoteCacheServer() == null)
+        {
+            // Not running in a remote cache server.
+            // Remove objects from the cache directly, as no need to broadcast removes to client machines...
+            cacheHub.getCache(cacheName).remove(key);
+        }
+        else
+        {
+            // Running in a remote cache server.
+            // Remove objects via the RemoteCacheServer API, so that removes will be broadcast to client machines...
+            try
+            {
+                Object keyToRemove = null;
+                CompositeCache<?, ?> cache = CompositeCacheManager.getInstance().getCache(cacheName);
+
+                // A String key was supplied, but to remove elements via the RemoteCacheServer API, we need the
+                // actual key object as stored in the cache (i.e. a Serializable object). To find the key in this form,
+                // we iterate through all keys stored in the memory cache until we find one whose toString matches
+                // the string supplied...
+                Set<?> allKeysInCache = cache.getMemoryCache().getKeySet();
+                for (Object keyInCache : allKeysInCache)
+                {
+                    if (keyInCache.toString().equals(key))
+                    {
+                        if (keyToRemove == null)
+                        {
+                            keyToRemove = keyInCache;
+                        }
+                        else
+                        {
+                            // A key matching the one specified was already found...
+                            throw new IllegalStateException("Unexpectedly found duplicate keys in the cache region matching the key specified.");
+                        }
+                    }
+                }
+                if (keyToRemove == null)
+                {
+                    throw new IllegalStateException("No match for this key could be found in the set of keys retrieved from the memory cache.");
+                }
+                // At this point, we have retrieved the matching K key.
+
+                // Call remoteCacheServer.remove(String, Serializable)...
+                RemoteCacheServer<Serializable, Serializable> remoteCacheServer = RemoteCacheServerFactory.getRemoteCacheServer();
+                remoteCacheServer.remove(cacheName, key);
+            }
+            catch (Exception e)
+            {
+                throw new IllegalStateException("Failed to remove element with key [" + key + ", " + key.getClass() + "] from cache region [" + cacheName + "]: " + e, e);
+            }
+        }
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/admin/JCSJMXBean.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/admin/JCSJMXBean.java
new file mode 100644
index 0000000..a686d42
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/admin/JCSJMXBean.java
@@ -0,0 +1,91 @@
+package org.apache.commons.jcs3.admin;
+
+import java.io.IOException;
+import java.util.List;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import javax.management.MXBean;
+
+/**
+ * A MXBean to expose the JCS statistics to JMX
+ */
+@MXBean
+public interface JCSJMXBean
+{
+    /**
+     * Builds up info about each element in a region.
+     * <p>
+     * @param cacheName
+     * @return List of CacheElementInfo objects
+     * @throws IOException
+     */
+    List<CacheElementInfo> buildElementInfo( String cacheName ) throws IOException;
+
+    /**
+     * Builds up data on every region.
+     * <p>
+     * TODO we need a most light weight method that does not count bytes. The byte counting can
+     *       really swamp a server.
+     * @return List of CacheRegionInfo objects
+     */
+    List<CacheRegionInfo> buildCacheInfo();
+
+    /**
+     * Tries to estimate how much data is in a region. This is expensive. If there are any non serializable objects in
+     * the region or an error occurs, suppresses exceptions and returns 0.
+     * <p>
+     *
+     * @return long The size of the region in bytes.
+     */
+    long getByteCount(String cacheName);
+
+    /**
+     * Clears all regions in the cache.
+     * <p>
+     * If this class is running within a remote cache server, clears all regions via the <code>RemoteCacheServer</code>
+     * API, so that removes will be broadcast to client machines. Otherwise clears all regions in the cache directly via
+     * the usual cache API.
+     */
+    void clearAllRegions() throws IOException;
+
+    /**
+     * Clears a particular cache region.
+     * <p>
+     * If this class is running within a remote cache server, clears the region via the <code>RemoteCacheServer</code>
+     * API, so that removes will be broadcast to client machines. Otherwise clears the region directly via the usual
+     * cache API.
+     */
+    void clearRegion(String cacheName) throws IOException;
+
+    /**
+     * Removes a particular item from a particular region.
+     * <p>
+     * If this class is running within a remote cache server, removes the item via the <code>RemoteCacheServer</code>
+     * API, so that removes will be broadcast to client machines. Otherwise clears the region directly via the usual
+     * cache API.
+     *
+     * @param cacheName
+     * @param key
+     *
+     * @throws IOException
+     */
+    void removeItem(String cacheName, String key) throws IOException;
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/AbstractAuxiliaryCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/AbstractAuxiliaryCache.java
new file mode 100644
index 0000000..8a1ed09
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/AbstractAuxiliaryCache.java
@@ -0,0 +1,257 @@
+package org.apache.commons.jcs3.auxiliary;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.engine.logging.CacheEvent;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEvent;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+import org.apache.commons.jcs3.engine.match.KeyMatcherPatternImpl;
+import org.apache.commons.jcs3.engine.match.behavior.IKeyMatcher;
+import org.apache.commons.jcs3.utils.serialization.StandardSerializer;
+
+/** This holds convenience methods used by most auxiliary caches. */
+public abstract class AbstractAuxiliaryCache<K, V>
+    implements AuxiliaryCache<K, V>
+{
+    /** An optional event logger */
+    private ICacheEventLogger cacheEventLogger;
+
+    /** The serializer. Uses a standard serializer by default. */
+    private IElementSerializer elementSerializer = new StandardSerializer();
+
+    /** Key matcher used by the getMatching API */
+    private IKeyMatcher<K> keyMatcher = new KeyMatcherPatternImpl<>();
+
+    /**
+     * Gets multiple items from the cache based on the given set of keys.
+     *
+     * @param keys
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache for any of these keys
+     */
+    protected Map<K, ICacheElement<K, V>> processGetMultiple(Set<K> keys) throws IOException
+    {
+        if (keys != null)
+        {
+            return keys.stream()
+                .map(key -> {
+                    try
+                    {
+                        return get(key);
+                    }
+                    catch (IOException e)
+                    {
+                        return null;
+                    }
+                })
+                .filter(element -> element != null)
+                .collect(Collectors.toMap(
+                        element -> element.getKey(),
+                        element -> element));
+        }
+
+        return new HashMap<>();
+    }
+
+    /**
+     * Gets the item from the cache.
+     *
+     * @param key
+     * @return ICacheElement, a wrapper around the key, value, and attributes
+     * @throws IOException
+     */
+    @Override
+    public abstract ICacheElement<K, V> get( K key ) throws IOException;
+
+    /**
+     * Logs an event if an event logger is configured.
+     * <p>
+     * @param item
+     * @param eventName
+     * @return ICacheEvent
+     */
+    protected ICacheEvent<K> createICacheEvent( ICacheElement<K, V> item, String eventName )
+    {
+        if ( cacheEventLogger == null )
+        {
+            return new CacheEvent<>();
+        }
+        String diskLocation = getEventLoggingExtraInfo();
+        String regionName = null;
+        K key = null;
+        if ( item != null )
+        {
+            regionName = item.getCacheName();
+            key = item.getKey();
+        }
+        return cacheEventLogger.createICacheEvent( getAuxiliaryCacheAttributes().getName(), regionName, eventName,
+                                                   diskLocation, key );
+    }
+
+    /**
+     * Logs an event if an event logger is configured.
+     * <p>
+     * @param regionName
+     * @param key
+     * @param eventName
+     * @return ICacheEvent
+     */
+    protected <T> ICacheEvent<T> createICacheEvent( String regionName, T key, String eventName )
+    {
+        if ( cacheEventLogger == null )
+        {
+            return new CacheEvent<>();
+        }
+        String diskLocation = getEventLoggingExtraInfo();
+        return cacheEventLogger.createICacheEvent( getAuxiliaryCacheAttributes().getName(), regionName, eventName,
+                                                   diskLocation, key );
+
+    }
+
+    /**
+     * Logs an event if an event logger is configured.
+     * <p>
+     * @param cacheEvent
+     */
+    protected <T> void logICacheEvent( ICacheEvent<T> cacheEvent )
+    {
+        if ( cacheEventLogger != null )
+        {
+            cacheEventLogger.logICacheEvent( cacheEvent );
+        }
+    }
+
+    /**
+     * Logs an event if an event logger is configured.
+     * <p>
+     * @param source
+     * @param eventName
+     * @param optionalDetails
+     */
+    protected void logApplicationEvent( String source, String eventName, String optionalDetails )
+    {
+        if ( cacheEventLogger != null )
+        {
+            cacheEventLogger.logApplicationEvent( source, eventName, optionalDetails );
+        }
+    }
+
+    /**
+     * Logs an event if an event logger is configured.
+     * <p>
+     * @param source
+     * @param eventName
+     * @param errorMessage
+     */
+    protected void logError( String source, String eventName, String errorMessage )
+    {
+        if ( cacheEventLogger != null )
+        {
+            cacheEventLogger.logError( source, eventName, errorMessage );
+        }
+    }
+
+    /**
+     * Gets the extra info for the event log.
+     * <p>
+     * @return IP, or disk location, etc.
+     */
+    public abstract String getEventLoggingExtraInfo();
+
+    /**
+     * Allows it to be injected.
+     * <p>
+     * @param cacheEventLogger
+     */
+    @Override
+    public void setCacheEventLogger( ICacheEventLogger cacheEventLogger )
+    {
+        this.cacheEventLogger = cacheEventLogger;
+    }
+
+    /**
+     * Allows it to be injected.
+     * <p>
+     * @return cacheEventLogger
+     */
+    public ICacheEventLogger getCacheEventLogger()
+    {
+        return this.cacheEventLogger;
+    }
+
+    /**
+     * Allows you to inject a custom serializer. A good example would be a compressing standard
+     * serializer.
+     * <p>
+     * Does not allow you to set it to null.
+     * <p>
+     * @param elementSerializer
+     */
+    @Override
+    public void setElementSerializer( IElementSerializer elementSerializer )
+    {
+        if ( elementSerializer != null )
+        {
+            this.elementSerializer = elementSerializer;
+        }
+    }
+
+    /**
+     * Allows it to be injected.
+     * <p>
+     * @return elementSerializer
+     */
+    public IElementSerializer getElementSerializer()
+    {
+        return this.elementSerializer;
+    }
+
+    /**
+     * Sets the key matcher used by get matching.
+     * <p>
+     * @param keyMatcher
+     */
+    @Override
+    public void setKeyMatcher( IKeyMatcher<K> keyMatcher )
+    {
+        if ( keyMatcher != null )
+        {
+            this.keyMatcher = keyMatcher;
+        }
+    }
+
+    /**
+     * Returns the key matcher used by get matching.
+     * <p>
+     * @return keyMatcher
+     */
+    public IKeyMatcher<K> getKeyMatcher()
+    {
+        return this.keyMatcher;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/AbstractAuxiliaryCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/AbstractAuxiliaryCacheAttributes.java
new file mode 100644
index 0000000..8792615
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/AbstractAuxiliaryCacheAttributes.java
@@ -0,0 +1,146 @@
+package org.apache.commons.jcs3.auxiliary;
+
+import org.apache.commons.jcs3.engine.behavior.ICacheEventQueue;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * This has common attributes used by all auxiliaries.
+ */
+public abstract class AbstractAuxiliaryCacheAttributes
+    implements AuxiliaryCacheAttributes
+{
+    /** Don't change */
+    private static final long serialVersionUID = -6594609334959187673L;
+
+    /** cacheName */
+    private String cacheName;
+
+    /** name */
+    private String name;
+
+    /** eventQueueType -- pooled, or single threaded */
+    private ICacheEventQueue.QueueType eventQueueType;
+
+    /** Named when pooled */
+    private String eventQueuePoolName;
+
+    /**
+     * @param name
+     */
+    @Override
+    public void setCacheName( String name )
+    {
+        this.cacheName = name;
+    }
+
+    /**
+     * Gets the cacheName attribute of the AuxiliaryCacheAttributes object
+     * <p>
+     * @return The cacheName value
+     */
+    @Override
+    public String getCacheName()
+    {
+        return this.cacheName;
+    }
+
+    /**
+     * This is the name of the auxiliary in configuration file.
+     * <p>
+     * @see org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes#setName(java.lang.String)
+     */
+    @Override
+    public void setName( String s )
+    {
+        this.name = s;
+    }
+
+    /**
+     * Gets the name attribute of the AuxiliaryCacheAttributes object
+     * <p>
+     * @return The name value
+     */
+    @Override
+    public String getName()
+    {
+        return this.name;
+    }
+
+    /**
+     * SINGLE is the default. If you choose POOLED, the value of EventQueuePoolName will be used
+     * <p>
+     * @param queueType SINGLE or POOLED
+     */
+    @Override
+    public void setEventQueueType( ICacheEventQueue.QueueType queueType )
+    {
+        this.eventQueueType = queueType;
+    }
+
+    /**
+     * @return SINGLE or POOLED
+     */
+    @Override
+    public ICacheEventQueue.QueueType getEventQueueType()
+    {
+        return eventQueueType;
+    }
+
+    /**
+     * If you choose a POOLED event queue type, the value of EventQueuePoolName will be used. This
+     * is ignored if the pool type is SINGLE
+     * <p>
+     * @param s SINGLE or POOLED
+     */
+    @Override
+    public void setEventQueuePoolName( String s )
+    {
+        eventQueuePoolName = s;
+    }
+
+    /**
+     * Sets the pool name to use. If a pool is not found by this name, the thread pool manager will
+     * return a default configuration.
+     * <p>
+     * @return name of thread pool to use for this auxiliary
+     */
+    @Override
+    public String getEventQueuePoolName()
+    {
+        return eventQueuePoolName;
+    }
+
+    /**
+     * @see java.lang.Object#clone()
+     */
+    @Override
+    public AbstractAuxiliaryCacheAttributes clone()
+    {
+        try
+        {
+            return (AbstractAuxiliaryCacheAttributes)super.clone();
+        }
+        catch (CloneNotSupportedException e)
+        {
+            throw new RuntimeException("Clone not supported. This should never happen.", e);
+        }
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/AbstractAuxiliaryCacheEventLogging.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/AbstractAuxiliaryCacheEventLogging.java
new file mode 100644
index 0000000..8b78f3b
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/AbstractAuxiliaryCacheEventLogging.java
@@ -0,0 +1,342 @@
+package org.apache.commons.jcs3.auxiliary;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEvent;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+
+/**
+ * All ICacheEvents are defined as final. Children must implement process events. These are wrapped
+ * in event log parent calls.
+ *
+ * You can override the public method, but if you don't, the default will call getWithTiming.
+ */
+public abstract class AbstractAuxiliaryCacheEventLogging<K, V>
+    extends AbstractAuxiliaryCache<K, V>
+{
+    /**
+     * Puts an item into the cache.
+     *
+     * @param cacheElement
+     * @throws IOException
+     */
+    @Override
+    public void update( ICacheElement<K, V> cacheElement )
+        throws IOException
+    {
+        updateWithEventLogging( cacheElement );
+    }
+
+    /**
+     * Puts an item into the cache. Wrapped in logging.
+     *
+     * @param cacheElement
+     * @throws IOException
+     */
+    protected final void updateWithEventLogging( ICacheElement<K, V> cacheElement )
+        throws IOException
+    {
+        ICacheEvent<K> cacheEvent = createICacheEvent( cacheElement, ICacheEventLogger.UPDATE_EVENT );
+        try
+        {
+            processUpdate( cacheElement );
+        }
+        finally
+        {
+            logICacheEvent( cacheEvent );
+        }
+    }
+
+    /**
+     * Implementation of put.
+     *
+     * @param cacheElement
+     * @throws IOException
+     */
+    protected abstract void processUpdate( ICacheElement<K, V> cacheElement )
+        throws IOException;
+
+    /**
+     * Gets the item from the cache.
+     *
+     * @param key
+     * @return ICacheElement, a wrapper around the key, value, and attributes
+     * @throws IOException
+     */
+    @Override
+    public ICacheElement<K, V> get( K key )
+        throws IOException
+    {
+        return getWithEventLogging( key );
+    }
+
+    /**
+     * Gets the item from the cache. Wrapped in logging.
+     *
+     * @param key
+     * @return ICacheElement, a wrapper around the key, value, and attributes
+     * @throws IOException
+     */
+    protected final ICacheElement<K, V> getWithEventLogging( K key )
+        throws IOException
+    {
+        ICacheEvent<K> cacheEvent = createICacheEvent( getCacheName(), key, ICacheEventLogger.GET_EVENT );
+        try
+        {
+            return processGet( key );
+        }
+        finally
+        {
+            logICacheEvent( cacheEvent );
+        }
+    }
+
+    /**
+     * Implementation of get.
+     *
+     * @param key
+     * @return ICacheElement, a wrapper around the key, value, and attributes
+     * @throws IOException
+     */
+    protected abstract ICacheElement<K, V> processGet( K key )
+        throws IOException;
+
+    /**
+     * Gets multiple items from the cache based on the given set of keys.
+     *
+     * @param keys
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache for any of these keys
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMultiple(Set<K> keys)
+        throws IOException
+    {
+        return getMultipleWithEventLogging( keys );
+    }
+
+    /**
+     * Gets multiple items from the cache based on the given set of keys.
+     *
+     * @param keys
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache for any of these keys
+     * @throws IOException
+     */
+    protected final Map<K, ICacheElement<K, V>> getMultipleWithEventLogging(Set<K> keys )
+        throws IOException
+    {
+        ICacheEvent<Serializable> cacheEvent = createICacheEvent( getCacheName(), (Serializable) keys,
+                                                    ICacheEventLogger.GETMULTIPLE_EVENT );
+        try
+        {
+            return processGetMultiple( keys );
+        }
+        finally
+        {
+            logICacheEvent( cacheEvent );
+        }
+    }
+
+    /**
+     * Gets items from the cache matching the given pattern. Items from memory will replace those
+     * from remote sources.
+     *
+     * This only works with string keys. It's too expensive to do a toString on every key.
+     *
+     * Auxiliaries will do their best to handle simple expressions. For instance, the JDBC disk
+     * cache will convert * to % and . to _
+     *
+     * @param pattern
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data matching the pattern.
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMatching( String pattern )
+        throws IOException
+    {
+        return getMatchingWithEventLogging( pattern );
+    }
+
+    /**
+     * Gets matching items from the cache based on the given pattern.
+     *
+     * @param pattern
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data matching the pattern.
+     * @throws IOException
+     */
+    protected final Map<K, ICacheElement<K, V>> getMatchingWithEventLogging( String pattern )
+        throws IOException
+    {
+        ICacheEvent<String> cacheEvent = createICacheEvent( getCacheName(), pattern, ICacheEventLogger.GETMATCHING_EVENT );
+        try
+        {
+            return processGetMatching( pattern );
+        }
+        finally
+        {
+            logICacheEvent( cacheEvent );
+        }
+    }
+
+    /**
+     * Implementation of getMatching.
+     *
+     * @param pattern
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data matching the pattern.
+     * @throws IOException
+     */
+    protected abstract Map<K, ICacheElement<K, V>> processGetMatching( String pattern )
+        throws IOException;
+
+    /**
+     * Removes the item from the cache. Wraps the remove in event logs.
+     *
+     * @param key
+     * @return boolean, whether or not the item was removed
+     * @throws IOException
+     */
+    @Override
+    public boolean remove( K key )
+        throws IOException
+    {
+        return removeWithEventLogging( key );
+    }
+
+    /**
+     * Removes the item from the cache. Wraps the remove in event logs.
+     *
+     * @param key
+     * @return boolean, whether or not the item was removed
+     * @throws IOException
+     */
+    protected final boolean removeWithEventLogging( K key )
+        throws IOException
+    {
+        ICacheEvent<K> cacheEvent = createICacheEvent( getCacheName(), key, ICacheEventLogger.REMOVE_EVENT );
+        try
+        {
+            return processRemove( key );
+        }
+        finally
+        {
+            logICacheEvent( cacheEvent );
+        }
+    }
+
+    /**
+     * Specific implementation of remove.
+     *
+     * @param key
+     * @return boolean, whether or not the item was removed
+     * @throws IOException
+     */
+    protected abstract boolean processRemove( K key )
+        throws IOException;
+
+    /**
+     * Removes all from the region. Wraps the removeAll in event logs.
+     *
+     * @throws IOException
+     */
+    @Override
+    public void removeAll()
+        throws IOException
+    {
+        removeAllWithEventLogging();
+    }
+
+    /**
+     * Removes all from the region. Wraps the removeAll in event logs.
+     *
+     * @throws IOException
+     */
+    protected final void removeAllWithEventLogging()
+        throws IOException
+    {
+        ICacheEvent<String> cacheEvent = createICacheEvent( getCacheName(), "all", ICacheEventLogger.REMOVEALL_EVENT );
+        try
+        {
+            processRemoveAll();
+        }
+        finally
+        {
+            logICacheEvent( cacheEvent );
+        }
+    }
+
+    /**
+     * Specific implementation of removeAll.
+     *
+     * @throws IOException
+     */
+    protected abstract void processRemoveAll()
+        throws IOException;
+
+    /**
+     * Synchronously dispose the remote cache; if failed, replace the remote handle with a zombie.
+     *
+     * @throws IOException
+     */
+    @Override
+    public void dispose()
+        throws IOException
+    {
+        disposeWithEventLogging();
+    }
+
+    /**
+     * Synchronously dispose the remote cache; if failed, replace the remote handle with a zombie.
+     * Wraps the removeAll in event logs.
+     *
+     * @throws IOException
+     */
+    protected final void disposeWithEventLogging()
+        throws IOException
+    {
+        ICacheEvent<String> cacheEvent = createICacheEvent( getCacheName(), "none", ICacheEventLogger.DISPOSE_EVENT );
+        try
+        {
+            processDispose();
+        }
+        finally
+        {
+            logICacheEvent( cacheEvent );
+        }
+    }
+
+    /**
+     * Specific implementation of dispose.
+     *
+     * @throws IOException
+     */
+    protected abstract void processDispose()
+        throws IOException;
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/AbstractAuxiliaryCacheFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/AbstractAuxiliaryCacheFactory.java
new file mode 100644
index 0000000..1399e4f
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/AbstractAuxiliaryCacheFactory.java
@@ -0,0 +1,72 @@
+package org.apache.commons.jcs3.auxiliary;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheFactory;
+
+/**
+ * Base class for auxiliary cache factories.
+ */
+public abstract class AbstractAuxiliaryCacheFactory
+    implements AuxiliaryCacheFactory
+{
+    /** The auxiliary name. The composite cache manager keeps this in a map, keyed by name. */
+    private String name = this.getClass().getSimpleName();
+
+    /**
+     * Initialize this factory
+     */
+    @Override
+    public void initialize()
+    {
+        // empty
+    }
+
+    /**
+     * Dispose of this factory, clean up shared resources
+     */
+    @Override
+    public void dispose()
+    {
+        // empty
+    }
+
+    /**
+     * Gets the name attribute of the DiskCacheFactory object
+     * <p>
+     * @return The name value
+     */
+    @Override
+    public String getName()
+    {
+        return this.name;
+    }
+
+    /**
+     * Sets the name attribute of the DiskCacheFactory object
+     * <p>
+     * @param name The new name value
+     */
+    @Override
+    public void setName( String name )
+    {
+        this.name = name;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/AbstractAuxiliaryCacheMonitor.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/AbstractAuxiliaryCacheMonitor.java
new file mode 100644
index 0000000..2595ddd
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/AbstractAuxiliaryCacheMonitor.java
@@ -0,0 +1,197 @@
+package org.apache.commons.jcs3.auxiliary;

+

+/*

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements.  See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership.  The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License.  You may obtain a copy of the License at

+ *

+ *   http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing,

+ * software distributed under the License is distributed on an

+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

+ * KIND, either express or implied.  See the License for the

+ * specific language governing permissions and limitations

+ * under the License.

+ */

+

+import java.util.concurrent.atomic.AtomicBoolean;

+import java.util.concurrent.locks.Condition;

+import java.util.concurrent.locks.Lock;

+import java.util.concurrent.locks.ReentrantLock;

+

+import org.apache.commons.jcs3.log.Log;

+import org.apache.commons.jcs3.log.LogManager;

+

+/**

+ * Used to monitor and repair any failed connection for the lateral cache service. By default the

+ * monitor operates in a failure driven mode. That is, it goes into a wait state until there is an

+ * error. Upon the notification of a connection error, the monitor changes to operate in a time

+ * driven mode. That is, it attempts to recover the connections on a periodic basis. When all failed

+ * connections are restored, it changes back to the failure driven mode.

+ */

+public abstract class AbstractAuxiliaryCacheMonitor extends Thread

+{

+    /** The logger */

+    protected final Log log = LogManager.getLog( this.getClass() );

+

+    /** How long to wait between runs */

+    protected static long idlePeriod = 20 * 1000;

+

+    /**

+     * Must make sure AbstractAuxiliaryCacheMonitor is started before any error can be detected!

+     */

+    protected AtomicBoolean allright = new AtomicBoolean(true);

+

+    /**

+     * shutdown flag

+     */

+    private final AtomicBoolean shutdown = new AtomicBoolean(false);

+

+    /** Synchronization helper lock */

+    private final Lock lock = new ReentrantLock();

+

+    /** Synchronization helper condition */

+    private final Condition trigger = lock.newCondition();

+

+    /**

+     * Constructor

+     *

+     * @param name the thread name

+     */

+    public AbstractAuxiliaryCacheMonitor(String name)

+    {

+        super(name);

+    }

+

+    /**

+     * Configures the idle period between repairs.

+     * <p>

+     * @param idlePeriod The new idlePeriod value

+     */

+    public static void setIdlePeriod( long idlePeriod )

+    {

+        if ( idlePeriod > AbstractAuxiliaryCacheMonitor.idlePeriod )

+        {

+            AbstractAuxiliaryCacheMonitor.idlePeriod = idlePeriod;

+        }

+    }

+

+    /**

+     * Notifies the cache monitor that an error occurred, and kicks off the error recovery process.

+     */

+    public void notifyError()

+    {

+        if (allright.compareAndSet(true, false))

+        {

+            signalTrigger();

+        }

+    }

+

+    /**

+     * Notifies the cache monitor that the service shall shut down

+     */

+    public void notifyShutdown()

+    {

+        if (shutdown.compareAndSet(false, true))

+        {

+            signalTrigger();

+        }

+    }

+

+    // Trigger continuation of loop

+    private void signalTrigger()

+    {

+        try

+        {

+            lock.lock();

+            trigger.signal();

+        }

+        finally

+        {

+            lock.unlock();

+        }

+    }

+

+    /**

+     * Clean up all resources before shutdown

+     */

+    protected abstract void dispose();

+

+    /**

+     * do actual work

+     */

+    protected abstract void doWork();

+

+    /**

+     * Main processing method for the AbstractAuxiliaryCacheMonitor object

+     */

+    @Override

+    public void run()

+    {

+        do

+        {

+            if ( allright.get() )

+            {

+                log.debug( "ERROR DRIVEN MODE: allright = true, cache monitor will wait for an error." );

+            }

+            else

+            {

+                log.debug( "ERROR DRIVEN MODE: allright = false cache monitor running." );

+            }

+

+            if ( allright.get() )

+            {

+                // Failure driven mode.

+                try

+                {

+                    lock.lock();

+                    trigger.await();

+                    // wake up only if there is an error.

+                }

+                catch ( InterruptedException ignore )

+                {

+                    //no op, this is expected

+                }

+                finally

+                {

+                    lock.unlock();

+                }

+            }

+

+            // check for requested shutdown

+            if ( shutdown.get() )

+            {

+                log.info( "Shutting down cache monitor" );

+                dispose();

+                return;

+            }

+

+            // The "allright" flag must be false here.

+            // Simply presume we can fix all the errors until proven otherwise.

+            allright.set(true);

+

+            log.debug( "Cache monitor running." );

+

+            doWork();

+

+            try

+            {

+                // don't want to sleep after waking from an error

+                // run immediately and sleep here.

+                log.debug( "Cache monitor sleeping for {0} between runs.", idlePeriod );

+

+                Thread.sleep( idlePeriod );

+            }

+            catch ( InterruptedException ex )

+            {

+                // ignore;

+            }

+        }

+        while ( true );

+    }

+}

diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/AuxiliaryCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/AuxiliaryCache.java
new file mode 100644
index 0000000..72ab8ce
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/AuxiliaryCache.java
@@ -0,0 +1,77 @@
+package org.apache.commons.jcs3.auxiliary;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.Set;
+
+import org.apache.commons.jcs3.engine.behavior.ICache;
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+import org.apache.commons.jcs3.engine.stats.behavior.IStats;
+
+/**
+ * Tag interface for auxiliary caches. Currently this provides no additional methods over what is in
+ * ICache, but I anticipate that will change. For example, there will be a mechanism for determining
+ * the type (disk/lateral/remote) of the auxiliary here -- and the existing getCacheType will be
+ * removed from ICache.
+ */
+public interface AuxiliaryCache<K, V>
+    extends ICache<K, V>
+{
+    /**
+     * Get a set of the keys for all elements in the auxiliary cache.
+     * <p>
+     * @return a set of the key type
+     * TODO This should probably be done in chunks with a range passed in. This
+     *       will be a problem if someone puts a 1,000,000 or so items in a
+     *       region.
+     * @throws IOException if access to the auxiliary cache fails
+     */
+    Set<K> getKeySet() throws IOException;
+
+    /**
+     * @return the historical and statistical data for a region's auxiliary cache.
+     */
+    IStats getStatistics();
+
+    /**
+     * This returns the generic attributes for an auxiliary cache. Most implementations will cast
+     * this to a more specific type.
+     * <p>
+     * @return the attributes for the auxiliary cache
+     */
+    AuxiliaryCacheAttributes getAuxiliaryCacheAttributes();
+
+    /**
+     * Allows you to inject a custom serializer. A good example would be a compressing standard
+     * serializer.
+     * <p>
+     * @param elementSerializer
+     */
+    void setElementSerializer( IElementSerializer elementSerializer );
+
+    /**
+     * Every Auxiliary must allow for the use of an event logger.
+     * <p>
+     * @param cacheEventLogger
+     */
+    void setCacheEventLogger( ICacheEventLogger cacheEventLogger );
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/AuxiliaryCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/AuxiliaryCacheAttributes.java
new file mode 100644
index 0000000..314831a
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/AuxiliaryCacheAttributes.java
@@ -0,0 +1,93 @@
+package org.apache.commons.jcs3.auxiliary;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.Serializable;
+
+import org.apache.commons.jcs3.engine.behavior.ICacheEventQueue;
+
+/**
+ * This is a nominal interface that auxiliary cache attributes should implement. This allows the
+ * auxiliary mangers to share a common interface.
+ */
+public interface AuxiliaryCacheAttributes
+    extends Serializable, Cloneable
+{
+    /**
+     * Sets the name of the cache, referenced by the appropriate manager.
+     * <p>
+     * @param s The new cacheName value
+     */
+    void setCacheName( String s );
+
+    /**
+     * Gets the cacheName attribute of the AuxiliaryCacheAttributes object
+     * <p>
+     * @return The cacheName value
+     */
+    String getCacheName();
+
+    /**
+     * Name known by by configurator
+     * <p>
+     * @param s The new name value
+     */
+    void setName( String s );
+
+    /**
+     * Gets the name attribute of the AuxiliaryCacheAttributes object
+     * <p>
+     * @return The name value
+     */
+    String getName();
+
+    /**
+     * SINGLE is the default. If you choose POOLED, the value of EventQueuePoolName will be used
+     * <p>
+     * @param s SINGLE or POOLED
+     */
+    void setEventQueueType( ICacheEventQueue.QueueType s );
+
+    /**
+     * @return SINGLE or POOLED
+     */
+    ICacheEventQueue.QueueType getEventQueueType();
+
+    /**
+     * If you choose a POOLED event queue type, the value of EventQueuePoolName will be used. This
+     * is ignored if the pool type is SINGLE
+     * <p>
+     * @param s SINGLE or POOLED
+     */
+    void setEventQueuePoolName( String s );
+
+    /**
+     * Sets the pool name to use. If a pool is not found by this name, the thread pool manager will
+     * return a default configuration.
+     * <p>
+     * @return name of thread pool to use for this auxiliary
+     */
+    String getEventQueuePoolName();
+
+    /**
+     * Clone object
+     */
+    AuxiliaryCacheAttributes clone();
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/AuxiliaryCacheConfigurator.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/AuxiliaryCacheConfigurator.java
new file mode 100644
index 0000000..2610785
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/AuxiliaryCacheConfigurator.java
@@ -0,0 +1,117 @@
+package org.apache.commons.jcs3.auxiliary;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Properties;
+
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.config.OptionConverter;
+import org.apache.commons.jcs3.utils.config.PropertySetter;
+import org.apache.commons.jcs3.utils.serialization.StandardSerializer;
+
+/**
+ * Configuration util for auxiliary caches. I plan to move the auxiliary configuration from the
+ * composite cache configurator here.
+ */
+public class AuxiliaryCacheConfigurator
+{
+    /** The logger. */
+    private static final Log log = LogManager.getLog( AuxiliaryCacheConfigurator.class );
+
+    /** .attributes */
+    public static final String ATTRIBUTE_PREFIX = ".attributes";
+
+    /**
+     * jcs.auxiliary.NAME.cacheeventlogger=CLASSNAME
+     * <p>
+     * jcs.auxiliary.NAME.cacheeventlogger.attributes.CUSTOMPROPERTY=VALUE
+     */
+    public static final String CACHE_EVENT_LOGGER_PREFIX = ".cacheeventlogger";
+
+    /**
+     * jcs.auxiliary.NAME.serializer=CLASSNAME
+     * <p>
+     * jcs.auxiliary.NAME.serializer.attributes.CUSTOMPROPERTY=VALUE
+     */
+    public static final String SERIALIZER_PREFIX = ".serializer";
+
+    /**
+     * Parses the event logger config, if there is any for the auxiliary.
+     * <p>
+     * @param props
+     * @param auxPrefix - ex. AUXILIARY_PREFIX + auxName
+     * @return cacheEventLogger
+     */
+    public static ICacheEventLogger parseCacheEventLogger( Properties props, String auxPrefix )
+    {
+        ICacheEventLogger cacheEventLogger = null;
+
+        // auxFactory was not previously initialized.
+        String eventLoggerClassName = auxPrefix + CACHE_EVENT_LOGGER_PREFIX;
+        cacheEventLogger = OptionConverter.instantiateByKey( props, eventLoggerClassName, null );
+        if ( cacheEventLogger != null )
+        {
+            String cacheEventLoggerAttributePrefix = auxPrefix + CACHE_EVENT_LOGGER_PREFIX + ATTRIBUTE_PREFIX;
+            PropertySetter.setProperties( cacheEventLogger, props, cacheEventLoggerAttributePrefix + "." );
+            log.info( "Using custom cache event logger [{0}] for auxiliary [{1}]",
+                    cacheEventLogger, auxPrefix );
+        }
+        else
+        {
+            log.info( "No cache event logger defined for auxiliary [{0}]", auxPrefix );
+        }
+        return cacheEventLogger;
+    }
+
+    /**
+     * Parses the element config, if there is any for the auxiliary.
+     * <p>
+     * @param props
+     * @param auxPrefix - ex. AUXILIARY_PREFIX + auxName
+     * @return cacheEventLogger
+     */
+    public static IElementSerializer parseElementSerializer( Properties props, String auxPrefix )
+    {
+        // TODO take in the entire prop key
+        IElementSerializer elementSerializer = null;
+
+        // auxFactory was not previously initialized.
+        String elementSerializerClassName = auxPrefix + SERIALIZER_PREFIX;
+        elementSerializer = OptionConverter.instantiateByKey( props, elementSerializerClassName, null );
+        if ( elementSerializer != null )
+        {
+            String attributePrefix = auxPrefix + SERIALIZER_PREFIX + ATTRIBUTE_PREFIX;
+            PropertySetter.setProperties( elementSerializer, props, attributePrefix + "." );
+            log.info( "Using custom element serializer [{0}] for auxiliary [{1}]",
+                    elementSerializer, auxPrefix );
+        }
+        else
+        {
+            // use the default standard serializer
+            elementSerializer = new StandardSerializer();
+            log.info( "Using standard serializer [{0}] for auxiliary [{1}]",
+                    elementSerializer, auxPrefix );
+        }
+        return elementSerializer;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/AuxiliaryCacheFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/AuxiliaryCacheFactory.java
new file mode 100644
index 0000000..ffaf2ae
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/AuxiliaryCacheFactory.java
@@ -0,0 +1,71 @@
+package org.apache.commons.jcs3.auxiliary;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+
+/**
+ * All auxiliary caches must have a factory that the cache configurator can use to create instances.
+ */
+public interface AuxiliaryCacheFactory
+{
+    /**
+     * Creates an auxiliary using the supplied attributes. Adds it to the composite cache manager.
+     *
+     * @param attr
+     * @param cacheMgr This allows auxiliaries to reference the manager without assuming that it is
+     *            a singleton. This will allow JCS to be a non-singleton. Also, it makes it easier to
+     *            test.
+     * @param cacheEventLogger
+     * @param elementSerializer
+     * @return AuxiliaryCache
+     * @throws Exception if cache instance could not be created
+     */
+    <K, V> AuxiliaryCache<K, V> createCache(
+            AuxiliaryCacheAttributes attr, ICompositeCacheManager cacheMgr,
+            ICacheEventLogger cacheEventLogger, IElementSerializer elementSerializer )
+            throws Exception;
+
+    /**
+     * Initialize this factory
+     */
+    void initialize();
+
+    /**
+     * Dispose of this factory, clean up shared resources
+     */
+    void dispose();
+
+    /**
+     * Sets the name attribute of the AuxiliaryCacheFactory object
+     *
+     * @param s The new name value
+     */
+    void setName( String s );
+
+    /**
+     * Gets the name attribute of the AuxiliaryCacheFactory object
+     *
+     * @return The name value
+     */
+    String getName();
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/AbstractDiskCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/AbstractDiskCache.java
new file mode 100644
index 0000000..d4c9308
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/AbstractDiskCache.java
@@ -0,0 +1,840 @@
+package org.apache.commons.jcs3.auxiliary.disk;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCacheEventLogging;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCache;
+import org.apache.commons.jcs3.auxiliary.disk.behavior.IDiskCacheAttributes;
+import org.apache.commons.jcs3.engine.CacheEventQueueFactory;
+import org.apache.commons.jcs3.engine.CacheInfo;
+import org.apache.commons.jcs3.engine.CacheStatus;
+import org.apache.commons.jcs3.engine.behavior.ICache;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheEventQueue;
+import org.apache.commons.jcs3.engine.behavior.ICacheListener;
+import org.apache.commons.jcs3.engine.stats.StatElement;
+import org.apache.commons.jcs3.engine.stats.Stats;
+import org.apache.commons.jcs3.engine.stats.behavior.IStatElement;
+import org.apache.commons.jcs3.engine.stats.behavior.IStats;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.struct.LRUMap;
+
+/**
+ * Abstract class providing a base implementation of a disk cache, which can be easily extended to
+ * implement a disk cache for a specific persistence mechanism.
+ *
+ * When implementing the abstract methods note that while this base class handles most things, it
+ * does not acquire or release any locks. Implementations should do so as necessary. This is mainly
+ * done to minimize the time spent in critical sections.
+ *
+ * Error handling in this class needs to be addressed. Currently if an exception is thrown by the
+ * persistence mechanism, this class destroys the event queue. Should it also destroy purgatory?
+ * Should it dispose itself?
+ */
+public abstract class AbstractDiskCache<K, V>
+    extends AbstractAuxiliaryCacheEventLogging<K, V>
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog( AbstractDiskCache.class );
+
+    /** Generic disk cache attributes */
+    private IDiskCacheAttributes diskCacheAttributes = null;
+
+    /**
+     * Map where elements are stored between being added to this cache and actually spooled to disk.
+     * This allows puts to the disk cache to return quickly, and the more expensive operation of
+     * serializing the elements to persistent storage queued for later.
+     *
+     * If the elements are pulled into the memory cache while the are still in purgatory, writing to
+     * disk can be canceled.
+     */
+    private Map<K, PurgatoryElement<K, V>> purgatory;
+
+    /**
+     * The CacheEventQueue where changes will be queued for asynchronous updating of the persistent
+     * storage.
+     */
+    private final ICacheEventQueue<K, V> cacheEventQueue;
+
+    /**
+     * Indicates whether the cache is 'alive': initialized, but not yet disposed. Child classes must
+     * set this to true.
+     */
+    private boolean alive = false;
+
+    /** Every cache will have a name, subclasses must set this when they are initialized. */
+    private final String cacheName;
+
+    /** DEBUG: Keeps a count of the number of purgatory hits for debug messages */
+    private int purgHits = 0;
+
+    /**
+     * We lock here, so that we cannot get an update after a remove all. an individual removal locks
+     * the item.
+     */
+    private final ReentrantReadWriteLock removeAllLock = new ReentrantReadWriteLock();
+
+    // ----------------------------------------------------------- constructors
+
+    /**
+     * Construct the abstract disk cache, create event queues and purgatory. Child classes should
+     * set the alive flag to true after they are initialized.
+     *
+     * @param attr
+     */
+    protected AbstractDiskCache( IDiskCacheAttributes attr )
+    {
+        this.diskCacheAttributes = attr;
+        this.cacheName = attr.getCacheName();
+
+        // create queue
+        CacheEventQueueFactory<K, V> fact = new CacheEventQueueFactory<>();
+        this.cacheEventQueue = fact.createCacheEventQueue( new MyCacheListener(), CacheInfo.listenerId, cacheName,
+                                                           diskCacheAttributes.getEventQueuePoolName(),
+                                                           diskCacheAttributes.getEventQueueType() );
+
+        // create purgatory
+        initPurgatory();
+    }
+
+    /**
+     * @return true if the cache is alive
+     */
+    public boolean isAlive()
+    {
+        return alive;
+    }
+
+    /**
+     * @param alive set the alive status
+     */
+    public void setAlive(boolean alive)
+    {
+        this.alive = alive;
+    }
+
+    /**
+     * Purgatory size of -1 means to use a HashMap with no size limit. Anything greater will use an
+     * LRU map of some sort.
+     *
+     * TODO Currently setting this to 0 will cause nothing to be put to disk, since it will assume
+     *       that if an item is not in purgatory, then it must have been plucked. We should make 0
+     *       work, a way to not use purgatory.
+     */
+    private void initPurgatory()
+    {
+        // we need this so we can stop the updates from happening after a
+        // removeall
+        removeAllLock.writeLock().lock();
+
+        try
+        {
+            synchronized (this)
+            {
+                if ( diskCacheAttributes.getMaxPurgatorySize() >= 0 )
+                {
+                    purgatory = new LRUMap<>( diskCacheAttributes.getMaxPurgatorySize() );
+                }
+                else
+                {
+                    purgatory = new HashMap<>();
+                }
+            }
+        }
+        finally
+        {
+            removeAllLock.writeLock().unlock();
+        }
+    }
+
+    // ------------------------------------------------------- interface ICache
+
+    /**
+     * Adds the provided element to the cache. Element will be added to purgatory, and then queued
+     * for later writing to the serialized storage mechanism.
+     *
+     * An update results in a put event being created. The put event will call the handlePut method
+     * defined here. The handlePut method calls the implemented doPut on the child.
+     *
+     * @param cacheElement
+     * @throws IOException
+     * @see org.apache.commons.jcs3.engine.behavior.ICache#update
+     */
+    @Override
+    public final void update( ICacheElement<K, V> cacheElement )
+        throws IOException
+    {
+        log.debug( "Putting element in purgatory, cacheName: {0}, key: {1}",
+                () -> cacheName, () -> cacheElement.getKey() );
+
+        try
+        {
+            // Wrap the CacheElement in a PurgatoryElement
+            PurgatoryElement<K, V> pe = new PurgatoryElement<>( cacheElement );
+
+            // Indicates the the element is eligible to be spooled to disk,
+            // this will remain true unless the item is pulled back into
+            // memory.
+            pe.setSpoolable( true );
+
+            // Add the element to purgatory
+            synchronized ( purgatory )
+            {
+                purgatory.put( pe.getKey(), pe );
+            }
+
+            // Queue element for serialization
+            cacheEventQueue.addPutEvent( pe );
+        }
+        catch ( IOException ex )
+        {
+            log.error( "Problem adding put event to queue.", ex );
+
+            cacheEventQueue.destroy();
+        }
+    }
+
+    /**
+     * Check to see if the item is in purgatory. If so, return it. If not, check to see if we have
+     * it on disk.
+     *
+     * @param key
+     * @return ICacheElement&lt;K, V&gt; or null
+     * @see AuxiliaryCache#get
+     */
+    @Override
+    public final ICacheElement<K, V> get( K key )
+    {
+        // If not alive, always return null.
+
+        if ( !alive )
+        {
+            log.debug( "get was called, but the disk cache is not alive." );
+            return null;
+        }
+
+        PurgatoryElement<K, V> pe = null;
+        synchronized ( purgatory )
+        {
+            pe = purgatory.get( key );
+        }
+
+        // If the element was found in purgatory
+        if ( pe != null )
+        {
+            purgHits++;
+
+            if ( purgHits % 100 == 0 )
+            {
+                log.debug( "Purgatory hits = {0}", purgHits );
+            }
+
+            // Since the element will go back to the memory cache, we could set
+            // spoolable to false, which will prevent the queue listener from
+            // serializing the element. This would not match the disk cache
+            // behavior and the behavior of other auxiliaries. Gets never remove
+            // items from auxiliaries.
+            // Beyond consistency, the items should stay in purgatory and get
+            // spooled since the mem cache may be set to 0. If an item is
+            // active, it will keep getting put into purgatory and removed. The
+            // CompositeCache now does not put an item to memory from disk if
+            // the size is 0.
+            // Do not set spoolable to false. Just let it go to disk. This
+            // will allow the memory size = 0 setting to work well.
+
+            log.debug( "Found element in purgatory, cacheName: {0}, key: {1}",
+                    cacheName, key );
+
+            return pe.getCacheElement();
+        }
+
+        // If we reach this point, element was not found in purgatory, so get
+        // it from the cache.
+        try
+        {
+            return doGet( key );
+        }
+        catch ( Exception e )
+        {
+            log.error( e );
+
+            cacheEventQueue.destroy();
+        }
+
+        return null;
+    }
+
+    /**
+     * Gets items from the cache matching the given pattern. Items from memory will replace those
+     * from remote sources.
+     *
+     * This only works with string keys. It's too expensive to do a toString on every key.
+     *
+     * Auxiliaries will do their best to handle simple expressions. For instance, the JDBC disk
+     * cache will convert * to % and . to _
+     *
+     * @param pattern
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data matching the pattern.
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMatching( String pattern )
+        throws IOException
+    {
+        // Get the keys from purgatory
+        Set<K> keyArray = null;
+
+        // this avoids locking purgatory, but it uses more memory
+        synchronized ( purgatory )
+        {
+            keyArray = new HashSet<>(purgatory.keySet());
+        }
+
+        Set<K> matchingKeys = getKeyMatcher().getMatchingKeysFromArray( pattern, keyArray );
+
+        // call getMultiple with the set
+        Map<K, ICacheElement<K, V>> result = processGetMultiple( matchingKeys );
+
+        // Get the keys from disk
+        Map<K, ICacheElement<K, V>> diskMatches = doGetMatching( pattern );
+
+        result.putAll( diskMatches );
+
+        return result;
+    }
+
+    /**
+     * The keys in the cache.
+     *
+     * @see org.apache.commons.jcs3.auxiliary.AuxiliaryCache#getKeySet()
+     */
+    @Override
+    public abstract Set<K> getKeySet() throws IOException;
+
+    /**
+     * Removes are not queued. A call to remove is immediate.
+     *
+     * @param key
+     * @return whether the item was present to be removed.
+     * @throws IOException
+     * @see org.apache.commons.jcs3.engine.behavior.ICache#remove
+     */
+    @Override
+    public final boolean remove( K key )
+        throws IOException
+    {
+        PurgatoryElement<K, V> pe = null;
+
+        synchronized ( purgatory )
+        {
+            // I'm getting the object, so I can lock on the element
+            // Remove element from purgatory if it is there
+            pe = purgatory.get( key );
+        }
+
+        if ( pe != null )
+        {
+            synchronized ( pe.getCacheElement() )
+            {
+                synchronized ( purgatory )
+                {
+                    purgatory.remove( key );
+                }
+
+                // no way to remove from queue, just make sure it doesn't get on
+                // disk and then removed right afterwards
+                pe.setSpoolable( false );
+
+                // Remove from persistent store immediately
+                doRemove( key );
+            }
+        }
+        else
+        {
+            // Remove from persistent store immediately
+            doRemove( key );
+        }
+
+        return false;
+    }
+
+    /**
+     * @throws IOException
+     * @see org.apache.commons.jcs3.engine.behavior.ICache#removeAll
+     */
+    @Override
+    public final void removeAll()
+        throws IOException
+    {
+        if ( this.diskCacheAttributes.isAllowRemoveAll() )
+        {
+            // Replace purgatory with a new empty hashtable
+            initPurgatory();
+
+            // Remove all from persistent store immediately
+            doRemoveAll();
+        }
+        else
+        {
+            log.info( "RemoveAll was requested but the request was not "
+                    + "fulfilled: allowRemoveAll is set to false." );
+        }
+    }
+
+    /**
+     * Adds a dispose request to the disk cache.
+     *
+     * Disposal proceeds in several steps.
+     * <ol>
+     * <li>Prior to this call the Composite cache dumped the memory into the disk cache. If it is
+     * large then we need to wait for the event queue to finish.</li>
+     * <li>Wait until the event queue is empty of until the configured ShutdownSpoolTimeLimit is
+     * reached.</li>
+     * <li>Call doDispose on the concrete impl.</li>
+     * </ol>
+     * @throws IOException
+     */
+    @Override
+    public final void dispose()
+        throws IOException
+    {
+        Thread t = new Thread(() ->
+        {
+            boolean keepGoing = true;
+            // long total = 0;
+            long interval = 100;
+            while ( keepGoing )
+            {
+                keepGoing = !cacheEventQueue.isEmpty();
+                try
+                {
+                    Thread.sleep( interval );
+                    // total += interval;
+                    // log.info( "total = " + total );
+                }
+                catch ( InterruptedException e )
+                {
+                    break;
+                }
+            }
+            log.info( "No longer waiting for event queue to finish: {0}",
+                    () -> cacheEventQueue.getStatistics() );
+        });
+        t.start();
+        // wait up to 60 seconds for dispose and then quit if not done.
+        try
+        {
+            t.join( this.diskCacheAttributes.getShutdownSpoolTimeLimit() * 1000L );
+        }
+        catch ( InterruptedException ex )
+        {
+            log.error( "The Shutdown Spool Process was interrupted.", ex );
+        }
+
+        log.info( "In dispose, destroying event queue." );
+        // This stops the processor thread.
+        cacheEventQueue.destroy();
+
+        // Invoke any implementation specific disposal code
+        // need to handle the disposal first.
+        doDispose();
+
+        alive = false;
+    }
+
+    /**
+     * @return the region name.
+     * @see ICache#getCacheName
+     */
+    @Override
+    public String getCacheName()
+    {
+        return cacheName;
+    }
+
+    /**
+     * Gets basic stats for the abstract disk cache.
+     *
+     * @return String
+     */
+    @Override
+    public String getStats()
+    {
+        return getStatistics().toString();
+    }
+
+    /**
+     * Returns semi-structured data.
+     *
+     * @see org.apache.commons.jcs3.auxiliary.AuxiliaryCache#getStatistics()
+     */
+    @Override
+    public IStats getStatistics()
+    {
+        IStats stats = new Stats();
+        stats.setTypeName( "Abstract Disk Cache" );
+
+        ArrayList<IStatElement<?>> elems = new ArrayList<>();
+
+        elems.add(new StatElement<>( "Purgatory Hits", Integer.valueOf(purgHits) ) );
+        elems.add(new StatElement<>( "Purgatory Size", Integer.valueOf(purgatory.size()) ) );
+
+        // get the stats from the event queue too
+        IStats eqStats = this.cacheEventQueue.getStatistics();
+        elems.addAll(eqStats.getStatElements());
+
+        stats.setStatElements( elems );
+
+        return stats;
+    }
+
+    /**
+     * @return the status -- alive or disposed from CacheConstants
+     * @see ICache#getStatus
+     */
+    @Override
+    public CacheStatus getStatus()
+    {
+        return ( alive ? CacheStatus.ALIVE : CacheStatus.DISPOSED );
+    }
+
+    /**
+     * Size cannot be determined without knowledge of the cache implementation, so subclasses will
+     * need to implement this method.
+     *
+     * @return the number of items.
+     * @see ICache#getSize
+     */
+    @Override
+    public abstract int getSize();
+
+    /**
+     * @see org.apache.commons.jcs3.engine.behavior.ICacheType#getCacheType
+     * @return Always returns DISK_CACHE since subclasses should all be of that type.
+     */
+    @Override
+    public CacheType getCacheType()
+    {
+        return CacheType.DISK_CACHE;
+    }
+
+    /**
+     * Cache that implements the CacheListener interface, and calls appropriate methods in its
+     * parent class.
+     */
+    protected class MyCacheListener
+        implements ICacheListener<K, V>
+    {
+        /** Id of the listener */
+        private long listenerId = 0;
+
+        /**
+         * @return cacheElement.getElementAttributes();
+         * @throws IOException
+         * @see ICacheListener#getListenerId
+         */
+        @Override
+        public long getListenerId()
+            throws IOException
+        {
+            return this.listenerId;
+        }
+
+        /**
+         * @param id
+         * @throws IOException
+         * @see ICacheListener#setListenerId
+         */
+        @Override
+        public void setListenerId( long id )
+            throws IOException
+        {
+            this.listenerId = id;
+        }
+
+        /**
+         * @param element
+         * @throws IOException
+         * @see ICacheListener#handlePut NOTE: This checks if the element is a puratory element and
+         *      behaves differently depending. However since we have control over how elements are
+         *      added to the cache event queue, that may not be needed ( they are always
+         *      PurgatoryElements ).
+         */
+        @Override
+        public void handlePut( ICacheElement<K, V> element )
+            throws IOException
+        {
+            if ( alive )
+            {
+                // If the element is a PurgatoryElement<K, V> we must check to see
+                // if it is still spoolable, and remove it from purgatory.
+                if ( element instanceof PurgatoryElement )
+                {
+                    PurgatoryElement<K, V> pe = (PurgatoryElement<K, V>) element;
+
+                    synchronized ( pe.getCacheElement() )
+                    {
+                        // TODO consider a timeout.
+                        // we need this so that we can have multiple update
+                        // threads and still have removeAll requests come in that
+                        // always win
+                        removeAllLock.readLock().lock();
+
+                        try
+                        {
+                            // TODO consider changing purgatory sync
+                            // String keyAsString = element.getKey().toString();
+                            synchronized ( purgatory )
+                            {
+                                // If the element has already been removed from
+                                // purgatory do nothing
+                                if ( !purgatory.containsKey( pe.getKey() ) )
+                                {
+                                    return;
+                                }
+
+                                element = pe.getCacheElement();
+                            }
+
+                            // I took this out of the purgatory sync block.
+                            // If the element is still eligible, spool it.
+                            if ( pe.isSpoolable() )
+                            {
+                                doUpdate( element );
+                            }
+                        }
+                        finally
+                        {
+                            removeAllLock.readLock().unlock();
+                        }
+
+                        synchronized ( purgatory )
+                        {
+                            // After the update has completed, it is safe to
+                            // remove the element from purgatory.
+                            purgatory.remove( element.getKey() );
+                        }
+                    }
+                }
+                else
+                {
+                    // call the child's implementation
+                    doUpdate( element );
+                }
+            }
+            else
+            {
+                /*
+                 * The cache is not alive, hence the element should be removed from purgatory. All
+                 * elements should be removed eventually. Perhaps, the alive check should have been
+                 * done before it went in the queue. This block handles the case where the disk
+                 * cache fails during normal operations.
+                 */
+                synchronized ( purgatory )
+                {
+                    purgatory.remove( element.getKey() );
+                }
+            }
+        }
+
+        /**
+         * @param cacheName
+         * @param key
+         * @throws IOException
+         * @see ICacheListener#handleRemove
+         */
+        @Override
+        public void handleRemove( String cacheName, K key )
+            throws IOException
+        {
+            if ( alive )
+            {
+                if ( doRemove( key ) )
+                {
+                    log.debug( "Element removed, key: " + key );
+                }
+            }
+        }
+
+        /**
+         * @param cacheName
+         * @throws IOException
+         * @see ICacheListener#handleRemoveAll
+         */
+        @Override
+        public void handleRemoveAll( String cacheName )
+            throws IOException
+        {
+            if ( alive )
+            {
+                doRemoveAll();
+            }
+        }
+
+        /**
+         * @param cacheName
+         * @throws IOException
+         * @see ICacheListener#handleDispose
+         */
+        @Override
+        public void handleDispose( String cacheName )
+            throws IOException
+        {
+            if ( alive )
+            {
+                doDispose();
+            }
+        }
+    }
+
+    /**
+     * Before the event logging layer, the subclasses implemented the do* methods. Now the do*
+     * methods call the *WithEventLogging method on the super. The *WithEventLogging methods call
+     * the abstract process* methods. The children implement the process methods.
+     *
+     * ex. doGet calls getWithEventLogging, which calls processGet
+     */
+
+    /**
+     * Get a value from the persistent store.
+     *
+     * Before the event logging layer, the subclasses implemented the do* methods. Now the do*
+     * methods call the *EventLogging method on the super. The *WithEventLogging methods call the
+     * abstract process* methods. The children implement the process methods.
+     *
+     * @param key Key to locate value for.
+     * @return An object matching key, or null.
+     * @throws IOException
+     */
+    protected final ICacheElement<K, V> doGet( K key )
+        throws IOException
+    {
+        return super.getWithEventLogging( key );
+    }
+
+    /**
+     * Get a value from the persistent store.
+     *
+     * Before the event logging layer, the subclasses implemented the do* methods. Now the do*
+     * methods call the *EventLogging method on the super. The *WithEventLogging methods call the
+     * abstract process* methods. The children implement the process methods.
+     *
+     * @param pattern Used to match keys.
+     * @return A map of matches..
+     * @throws IOException
+     */
+    protected final Map<K, ICacheElement<K, V>> doGetMatching( String pattern )
+        throws IOException
+    {
+        return super.getMatchingWithEventLogging( pattern );
+    }
+
+    /**
+     * Add a cache element to the persistent store.
+     *
+     * Before the event logging layer, the subclasses implemented the do* methods. Now the do*
+     * methods call the *EventLogging method on the super. The *WithEventLogging methods call the
+     * abstract process* methods. The children implement the process methods.
+     *
+     * @param cacheElement
+     * @throws IOException
+     */
+    protected final void doUpdate( ICacheElement<K, V> cacheElement )
+        throws IOException
+    {
+        super.updateWithEventLogging( cacheElement );
+    }
+
+    /**
+     * Remove an object from the persistent store if found.
+     *
+     * Before the event logging layer, the subclasses implemented the do* methods. Now the do*
+     * methods call the *EventLogging method on the super. The *WithEventLogging methods call the
+     * abstract process* methods. The children implement the process methods.
+     *
+     * @param key Key of object to remove.
+     * @return whether or no the item was present when removed
+     * @throws IOException
+     */
+    protected final boolean doRemove( K key )
+        throws IOException
+    {
+        return super.removeWithEventLogging( key );
+    }
+
+    /**
+     * Remove all objects from the persistent store.
+     *
+     * Before the event logging layer, the subclasses implemented the do* methods. Now the do*
+     * methods call the *EventLogging method on the super. The *WithEventLogging methods call the
+     * abstract process* methods. The children implement the process methods.
+     *
+     * @throws IOException
+     */
+    protected final void doRemoveAll()
+        throws IOException
+    {
+        super.removeAllWithEventLogging();
+    }
+
+    /**
+     * Dispose of the persistent store. Note that disposal of purgatory and setting alive to false
+     * does NOT need to be done by this method.
+     *
+     * Before the event logging layer, the subclasses implemented the do* methods. Now the do*
+     * methods call the *EventLogging method on the super. The *WithEventLogging methods call the
+     * abstract process* methods. The children implement the process methods.
+     *
+     * @throws IOException
+     */
+    protected final void doDispose()
+        throws IOException
+    {
+        super.disposeWithEventLogging();
+    }
+
+    /**
+     * Gets the extra info for the event log.
+     *
+     * @return disk location
+     */
+    @Override
+    public String getEventLoggingExtraInfo()
+    {
+        return getDiskLocation();
+    }
+
+    /**
+     * This is used by the event logging.
+     *
+     * @return the location of the disk, either path or ip.
+     */
+    protected abstract String getDiskLocation();
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/AbstractDiskCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/AbstractDiskCacheAttributes.java
new file mode 100644
index 0000000..266c922
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/AbstractDiskCacheAttributes.java
@@ -0,0 +1,221 @@
+package org.apache.commons.jcs3.auxiliary.disk;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.File;
+
+import org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.disk.behavior.IDiskCacheAttributes;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * This has common attributes that any conceivable disk cache would need.
+ */
+public abstract class AbstractDiskCacheAttributes extends AbstractAuxiliaryCacheAttributes implements IDiskCacheAttributes
+{
+    /** Don't change. */
+    private static final long serialVersionUID = 8306631920391711229L;
+
+    /** The logger */
+    private static final Log log = LogManager.getLog(AbstractDiskCacheAttributes.class);
+
+    /** path to disk */
+    private File diskPath;
+
+    /** if this is false, we will not execute remove all */
+    private boolean allowRemoveAll = true;
+
+    /** default to 5000 */
+    private int maxPurgatorySize = MAX_PURGATORY_SIZE_DEFAULT;
+
+    /** Default amount of time to allow for key persistence on shutdown */
+    private static final int DEFAULT_shutdownSpoolTimeLimit = 60;
+
+    /**
+     * This default determines how long the shutdown will wait for the key spool and data defrag to
+     * finish.
+     */
+    private int shutdownSpoolTimeLimit = DEFAULT_shutdownSpoolTimeLimit;
+
+    /** Type of disk limit: SIZE or COUNT */
+    private DiskLimitType diskLimitType = DiskLimitType.COUNT;
+
+    /**
+     * Sets the diskPath attribute of the DiskCacheAttributes object
+     * <p>
+     *
+     * @param path
+     *            The new diskPath value
+     */
+    @Override
+    public void setDiskPath(String path)
+    {
+        setDiskPath(new File(path));
+    }
+
+    /**
+     * Sets the diskPath attribute of the DiskCacheAttributes object
+     * <p>
+     *
+     * @param diskPath
+     *            The new diskPath value
+     */
+    public void setDiskPath(File diskPath)
+    {
+        this.diskPath = diskPath;
+        boolean result = this.diskPath.isDirectory();
+
+        if (!result)
+        {
+            result = this.diskPath.mkdirs();
+        }
+        if (!result)
+        {
+            log.error("Failed to create directory {0}", diskPath);
+        }
+    }
+
+    /**
+     * Gets the diskPath attribute of the attributes object
+     * <p>
+     *
+     * @return The diskPath value
+     */
+    @Override
+    public File getDiskPath()
+    {
+        return this.diskPath;
+    }
+
+    /**
+     * Gets the maxKeySize attribute of the DiskCacheAttributes object
+     * <p>
+     *
+     * @return The maxPurgatorySize value
+     */
+    @Override
+    public int getMaxPurgatorySize()
+    {
+        return maxPurgatorySize;
+    }
+
+    /**
+     * Sets the maxPurgatorySize attribute of the DiskCacheAttributes object
+     * <p>
+     *
+     * @param maxPurgatorySize
+     *            The new maxPurgatorySize value
+     */
+    @Override
+    public void setMaxPurgatorySize(int maxPurgatorySize)
+    {
+        this.maxPurgatorySize = maxPurgatorySize;
+    }
+
+    /**
+     * Get the amount of time in seconds we will wait for elements to move to disk during shutdown
+     * for a particular region.
+     * <p>
+     *
+     * @return the time in seconds.
+     */
+    @Override
+    public int getShutdownSpoolTimeLimit()
+    {
+        return this.shutdownSpoolTimeLimit;
+    }
+
+    /**
+     * Sets the amount of time in seconds we will wait for elements to move to disk during shutdown
+     * for a particular region.
+     * <p>
+     * This is how long we give the event queue to empty.
+     * <p>
+     * The default is 60 seconds.
+     * <p>
+     *
+     * @param shutdownSpoolTimeLimit
+     *            the time in seconds
+     */
+    @Override
+    public void setShutdownSpoolTimeLimit(int shutdownSpoolTimeLimit)
+    {
+        this.shutdownSpoolTimeLimit = shutdownSpoolTimeLimit;
+    }
+
+    /**
+     * @param allowRemoveAll
+     *            The allowRemoveAll to set.
+     */
+    @Override
+    public void setAllowRemoveAll(boolean allowRemoveAll)
+    {
+        this.allowRemoveAll = allowRemoveAll;
+    }
+
+    /**
+     * @return Returns the allowRemoveAll.
+     */
+    @Override
+    public boolean isAllowRemoveAll()
+    {
+        return allowRemoveAll;
+    }
+
+    /**
+     * Includes the common attributes for a debug message.
+     * <p>
+     *
+     * @return String
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder str = new StringBuilder();
+        str.append("AbstractDiskCacheAttributes ");
+        str.append("\n diskPath = " + getDiskPath());
+        str.append("\n maxPurgatorySize   = " + getMaxPurgatorySize());
+        str.append("\n allowRemoveAll   = " + isAllowRemoveAll());
+        str.append("\n ShutdownSpoolTimeLimit   = " + getShutdownSpoolTimeLimit());
+        return str.toString();
+    }
+
+    @Override
+    public void setDiskLimitType(DiskLimitType diskLimitType)
+    {
+        this.diskLimitType = diskLimitType;
+    }
+
+    @Override
+    public void setDiskLimitTypeName(String diskLimitTypeName)
+    {
+        if (diskLimitTypeName != null)
+        {
+            diskLimitType = DiskLimitType.valueOf(diskLimitTypeName.trim());
+        }
+    }
+
+    @Override
+    public DiskLimitType getDiskLimitType()
+    {
+        return diskLimitType;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/PurgatoryElement.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/PurgatoryElement.java
new file mode 100644
index 0000000..f2babab
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/PurgatoryElement.java
@@ -0,0 +1,156 @@
+package org.apache.commons.jcs3.auxiliary.disk;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+
+/**
+ * Implementation of cache elements in purgatory.
+ *
+ * Elements are stored in purgatory when they are spooled to the auxiliary cache, but have not yet
+ * been written to disk.
+ */
+public class PurgatoryElement<K, V>
+    extends CacheElement<K, V>
+{
+    /** Don't change */
+    private static final long serialVersionUID = -8152034342684135628L;
+
+    /** Is the element ready to be spooled? */
+    private boolean spoolable = false;
+
+    /** Wrapped cache Element */
+    private final ICacheElement<K, V> cacheElement;
+
+    /**
+     * Constructor for the PurgatoryElement&lt;K, V&gt; object
+     *
+     * @param cacheElement CacheElement
+     */
+    public PurgatoryElement( ICacheElement<K, V> cacheElement )
+    {
+        super(cacheElement.getCacheName(),
+                cacheElement.getKey(), cacheElement.getVal(),
+                cacheElement.getElementAttributes());
+        this.cacheElement = cacheElement;
+    }
+
+    /**
+     * Gets the spoolable property.
+     *
+     * @return The spoolable value
+     */
+    public boolean isSpoolable()
+    {
+        return spoolable;
+    }
+
+    /**
+     * Sets the spoolable property.
+     *
+     * @param spoolable The new spoolable value
+     */
+    public void setSpoolable( boolean spoolable )
+    {
+        this.spoolable = spoolable;
+    }
+
+    /**
+     * Get the wrapped cache element.
+     *
+     * @return ICacheElement
+     */
+    public ICacheElement<K, V> getCacheElement()
+    {
+        return cacheElement;
+    }
+
+    // ------------------------------------------------ interface ICacheElement
+
+    /**
+     * @return cacheElement.getCacheName();
+     * @see ICacheElement#getCacheName
+     */
+    @Override
+    public String getCacheName()
+    {
+        return cacheElement.getCacheName();
+    }
+
+    /**
+     * @return cacheElement.getKey();
+     * @see ICacheElement#getKey
+     */
+    @Override
+    public K getKey()
+    {
+        return cacheElement.getKey();
+    }
+
+    /**
+     * @return cacheElement.getVal();
+     * @see ICacheElement#getVal
+     */
+    @Override
+    public V getVal()
+    {
+        return cacheElement.getVal();
+    }
+
+    /**
+     * @return cacheElement.getElementAttributes();
+     * @see ICacheElement#getElementAttributes
+     */
+    @Override
+    public IElementAttributes getElementAttributes()
+    {
+        return cacheElement.getElementAttributes();
+    }
+
+    /**
+     * @param attr
+     * @see ICacheElement#setElementAttributes
+     */
+    @Override
+    public void setElementAttributes( IElementAttributes attr )
+    {
+        cacheElement.setElementAttributes( attr );
+    }
+
+    /**
+     * @return debug string
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        buf.append( "[PurgatoryElement: " );
+        buf.append( " isSpoolable = " + isSpoolable() );
+        buf.append( " CacheElement = " + getCacheElement() );
+        buf.append( " CacheName = " + getCacheName() );
+        buf.append( " Key = " + getKey() );
+        buf.append( " Value = " + getVal() );
+        buf.append( " ElementAttributes = " + getElementAttributes() );
+        buf.append( "]" );
+        return buf.toString();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/behavior/IDiskCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/behavior/IDiskCacheAttributes.java
new file mode 100644
index 0000000..7d52fef
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/behavior/IDiskCacheAttributes.java
@@ -0,0 +1,131 @@
+package org.apache.commons.jcs3.auxiliary.disk.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.File;
+
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
+
+/**
+ * Common disk cache attributes.
+ */
+public interface IDiskCacheAttributes
+    extends AuxiliaryCacheAttributes
+{
+    enum DiskLimitType {
+        /** limit elements by count (default) */
+        COUNT,
+        /** limit elements by their size */
+        SIZE
+    }
+    /**
+     * This is the default purgatory size limit. Purgatory is the area where
+     * items to be spooled are temporarily stored. It basically provides access
+     * to items on the to-be-spooled queue.
+     */
+    int MAX_PURGATORY_SIZE_DEFAULT = 5000;
+
+    /**
+     * Sets the diskPath attribute of the IJISPCacheAttributes object
+     * <p>
+     * @param path
+     *            The new diskPath value
+     */
+    void setDiskPath( String path );
+
+    /**
+     * Gets the diskPath attribute of the attributes object
+     * <p>
+     * @return The diskPath value
+     */
+    File getDiskPath();
+
+    /**
+     * Gets the maxKeySize attribute of the DiskCacheAttributes object
+     * <p>
+     * @return The maxPurgatorySize value
+     */
+    int getMaxPurgatorySize();
+
+    /**
+     * Sets the maxPurgatorySize attribute of the DiskCacheAttributes object
+     * <p>
+     * @param maxPurgatorySize
+     *            The new maxPurgatorySize value
+     */
+    void setMaxPurgatorySize( int maxPurgatorySize );
+
+    /**
+     * Get the amount of time in seconds we will wait for elements to move to
+     * disk during shutdown for a particular region.
+     * <p>
+     * @return the time in seconds.
+     */
+    int getShutdownSpoolTimeLimit();
+
+    /**
+     * Sets the amount of time in seconds we will wait for elements to move to
+     * disk during shutdown for a particular region.
+     * <p>
+     * This is how long we give the event queue to empty.
+     * <p>
+     * The default is 60 seconds.
+     * <p>
+     * @param shutdownSpoolTimeLimit
+     *            the time in seconds
+     */
+    void setShutdownSpoolTimeLimit( int shutdownSpoolTimeLimit );
+
+    /**
+     * If this is true then remove all is not prohibited.
+     * <p>
+     * @return boolean
+     */
+    boolean isAllowRemoveAll();
+
+    /**
+     * If this is false, then remove all requests will not be honored.
+     * <p>
+     * This provides a safety mechanism for the persistent store.
+     * <p>
+     * @param allowRemoveAll
+     */
+    void setAllowRemoveAll( boolean allowRemoveAll );
+
+    /**
+     * set the type of the limit of the cache size
+     * @param diskLimitType COUNT - limit by count of the elements, SIZE, limit by sum of element's size
+     */
+    void setDiskLimitType(DiskLimitType diskLimitType);
+
+    /**
+     * Translates and stores String values  of DiskLimitType
+     *
+     * Allowed values: "COUNT" and "SIZE"
+     * @param diskLimitTypeName
+     */
+    void setDiskLimitTypeName(String diskLimitTypeName);
+
+    /**
+     *
+     * @return active DiskLimitType
+     */
+    DiskLimitType getDiskLimitType();
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDisk.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDisk.java
new file mode 100644
index 0000000..517a9bf
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDisk.java
@@ -0,0 +1,517 @@
+package org.apache.commons.jcs3.auxiliary.disk.block;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.file.StandardOpenOption;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.serialization.StandardSerializer;
+
+/**
+ * This class manages reading an writing data to disk. When asked to write a value, it returns a
+ * block array. It can read an object from the block numbers in a byte array.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class BlockDisk implements AutoCloseable
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog(BlockDisk.class);
+
+    /** The size of the header that indicates the amount of data stored in an occupied block. */
+    public static final byte HEADER_SIZE_BYTES = 4;
+    // N.B. 4 bytes is the size used for ByteBuffer.putInt(int value) and ByteBuffer.getInt()
+
+    /** defaults to 4kb */
+    private static final int DEFAULT_BLOCK_SIZE_BYTES = 4 * 1024;
+
+    /** Size of the blocks */
+    private final int blockSizeBytes;
+
+    /**
+     * the total number of blocks that have been used. If there are no free, we will use this to
+     * calculate the position of the next block.
+     */
+    private final AtomicInteger numberOfBlocks = new AtomicInteger(0);
+
+    /** Empty blocks that can be reused. */
+    private final ConcurrentLinkedQueue<Integer> emptyBlocks = new ConcurrentLinkedQueue<>();
+
+    /** The serializer. */
+    private final IElementSerializer elementSerializer;
+
+    /** Location of the spot on disk */
+    private final String filepath;
+
+    /** File channel for multiple concurrent reads and writes */
+    private final FileChannel fc;
+
+    /** How many bytes have we put to disk */
+    private final AtomicLong putBytes = new AtomicLong(0);
+
+    /** How many items have we put to disk */
+    private final AtomicLong putCount = new AtomicLong(0);
+
+    /**
+     * Constructor for the Disk object
+     * <p>
+     * @param file
+     * @param elementSerializer
+     * @throws IOException
+     */
+    public BlockDisk(File file, IElementSerializer elementSerializer)
+        throws IOException
+    {
+        this(file, DEFAULT_BLOCK_SIZE_BYTES, elementSerializer);
+    }
+
+    /**
+     * Creates the file and set the block size in bytes.
+     * <p>
+     * @param file
+     * @param blockSizeBytes
+     * @throws IOException
+     */
+    public BlockDisk(File file, int blockSizeBytes)
+        throws IOException
+    {
+        this(file, blockSizeBytes, new StandardSerializer());
+    }
+
+    /**
+     * Creates the file and set the block size in bytes.
+     * <p>
+     * @param file
+     * @param blockSizeBytes
+     * @param elementSerializer
+     * @throws IOException
+     */
+    public BlockDisk(File file, int blockSizeBytes, IElementSerializer elementSerializer)
+        throws IOException
+    {
+        this.filepath = file.getAbsolutePath();
+        this.fc = FileChannel.open(file.toPath(),
+                StandardOpenOption.CREATE,
+                StandardOpenOption.READ,
+                StandardOpenOption.WRITE);
+        this.numberOfBlocks.set((int) Math.ceil(1f * this.fc.size() / blockSizeBytes));
+
+        log.info("Constructing BlockDisk, blockSizeBytes [{0}]", blockSizeBytes);
+
+        this.blockSizeBytes = blockSizeBytes;
+        this.elementSerializer = elementSerializer;
+    }
+
+    /**
+     * Allocate a given number of blocks from the available set
+     *
+     * @param numBlocksNeeded
+     * @return an array of allocated blocks
+     */
+    private int[] allocateBlocks(int numBlocksNeeded)
+    {
+        assert numBlocksNeeded >= 1;
+
+        int[] blocks = new int[numBlocksNeeded];
+        // get them from the empty list or take the next one
+        for (int i = 0; i < numBlocksNeeded; i++)
+        {
+            Integer emptyBlock = emptyBlocks.poll();
+            if (emptyBlock == null)
+            {
+                emptyBlock = Integer.valueOf(numberOfBlocks.getAndIncrement());
+            }
+            blocks[i] = emptyBlock.intValue();
+        }
+
+        return blocks;
+    }
+
+    /**
+     * This writes an object to disk and returns the blocks it was stored in.
+     * <p>
+     * The program flow is as follows:
+     * <ol>
+     * <li>Serialize the object.</li>
+     * <li>Determine the number of blocks needed.</li>
+     * <li>Look for free blocks in the emptyBlock list.</li>
+     * <li>If there were not enough in the empty list. Take the nextBlock and increment it.</li>
+     * <li>If the data will not fit in one block, create sub arrays.</li>
+     * <li>Write the subarrays to disk.</li>
+     * <li>If the process fails we should decrement the block count if we took from it.</li>
+     * </ol>
+     * @param object
+     * @return the blocks we used.
+     * @throws IOException
+     */
+    protected <T> int[] write(T object)
+        throws IOException
+    {
+        // serialize the object
+        byte[] data = elementSerializer.serialize(object);
+
+        log.debug("write, total pre-chunking data.length = {0}", data.length);
+
+        this.putBytes.addAndGet(data.length);
+        this.putCount.incrementAndGet();
+
+        // figure out how many blocks we need.
+        int numBlocksNeeded = calculateTheNumberOfBlocksNeeded(data);
+
+        log.debug("numBlocksNeeded = {0}", numBlocksNeeded);
+
+        // allocate blocks
+        int[] blocks = allocateBlocks(numBlocksNeeded);
+
+        int offset = 0;
+        final int maxChunkSize = blockSizeBytes - HEADER_SIZE_BYTES;
+        ByteBuffer headerBuffer = ByteBuffer.allocate(HEADER_SIZE_BYTES);
+        ByteBuffer dataBuffer = ByteBuffer.wrap(data);
+
+        for (int i = 0; i < numBlocksNeeded; i++)
+        {
+            headerBuffer.clear();
+            int length = Math.min(maxChunkSize, data.length - offset);
+            headerBuffer.putInt(length);
+            headerBuffer.flip();
+
+            dataBuffer.position(offset).limit(offset + length);
+            ByteBuffer slice = dataBuffer.slice();
+
+            long position = calculateByteOffsetForBlockAsLong(blocks[i]);
+            // write the header
+            int written = fc.write(headerBuffer, position);
+            assert written == HEADER_SIZE_BYTES;
+
+            //write the data
+            written = fc.write(slice, position + HEADER_SIZE_BYTES);
+            assert written == length;
+
+            offset += length;
+        }
+
+        //fc.force(false);
+
+        return blocks;
+    }
+
+    /**
+     * Return the amount to put in each block. Fill them all the way, minus the header.
+     * <p>
+     * @param complete
+     * @param numBlocksNeeded
+     * @return byte[][]
+     */
+    protected byte[][] getBlockChunks(byte[] complete, int numBlocksNeeded)
+    {
+        byte[][] chunks = new byte[numBlocksNeeded][];
+
+        if (numBlocksNeeded == 1)
+        {
+            chunks[0] = complete;
+        }
+        else
+        {
+            int maxChunkSize = this.blockSizeBytes - HEADER_SIZE_BYTES;
+            int totalBytes = complete.length;
+            int totalUsed = 0;
+            for (short i = 0; i < numBlocksNeeded; i++)
+            {
+                // use the max that can be written to a block or whatever is left in the original
+                // array
+                int chunkSize = Math.min(maxChunkSize, totalBytes - totalUsed);
+                byte[] chunk = new byte[chunkSize];
+                // copy from the used position to the chunk size on the complete array to the chunk
+                // array.
+                System.arraycopy(complete, totalUsed, chunk, 0, chunkSize);
+                chunks[i] = chunk;
+                totalUsed += chunkSize;
+            }
+        }
+
+        return chunks;
+    }
+
+    /**
+     * Reads an object that is located in the specified blocks.
+     * <p>
+     * @param blockNumbers
+     * @return the object instance
+     * @throws IOException
+     * @throws ClassNotFoundException
+     */
+    protected <T> T read(int[] blockNumbers)
+        throws IOException, ClassNotFoundException
+    {
+        final ByteBuffer data;
+
+        if (blockNumbers.length == 1)
+        {
+            data = readBlock(blockNumbers[0]);
+        }
+        else
+        {
+            data = ByteBuffer.allocate(blockNumbers.length * getBlockSizeBytes());
+            // get all the blocks into data
+            for (short i = 0; i < blockNumbers.length; i++)
+            {
+                ByteBuffer chunk = readBlock(blockNumbers[i]);
+                data.put(chunk);
+            }
+
+            data.flip();
+        }
+
+        log.debug("read, total post combination data.length = {0}", () -> data.limit());
+
+        return elementSerializer.deSerialize(data.array(), null);
+    }
+
+    /**
+     * This reads the occupied data in a block.
+     * <p>
+     * The first four bytes of the record should tell us how long it is. The data is read into a
+     * byte array and then an object is constructed from the byte array.
+     * <p>
+     * @return byte[]
+     * @param block
+     * @throws IOException
+     */
+    private ByteBuffer readBlock(int block)
+        throws IOException
+    {
+        int datalen = 0;
+
+        String message = null;
+        boolean corrupted = false;
+        long fileLength = fc.size();
+
+        long position = calculateByteOffsetForBlockAsLong(block);
+//        if (position > fileLength)
+//        {
+//            corrupted = true;
+//            message = "Record " + position + " starts past EOF.";
+//        }
+//        else
+        {
+            ByteBuffer datalength = ByteBuffer.allocate(HEADER_SIZE_BYTES);
+            fc.read(datalength, position);
+            datalength.flip();
+            datalen = datalength.getInt();
+            if (position + datalen > fileLength)
+            {
+                corrupted = true;
+                message = "Record " + position + " exceeds file length.";
+            }
+        }
+
+        if (corrupted)
+        {
+            log.warn("\n The file is corrupt: \n {0}", message);
+            throw new IOException("The File Is Corrupt, need to reset");
+        }
+
+        ByteBuffer data = ByteBuffer.allocate(datalen);
+        fc.read(data, position + HEADER_SIZE_BYTES);
+        data.flip();
+
+        return data;
+    }
+
+    /**
+     * Add these blocks to the emptyBlock list.
+     * <p>
+     * @param blocksToFree
+     */
+    protected void freeBlocks(int[] blocksToFree)
+    {
+        if (blocksToFree != null)
+        {
+            for (short i = 0; i < blocksToFree.length; i++)
+            {
+                emptyBlocks.offer(Integer.valueOf(blocksToFree[i]));
+            }
+        }
+    }
+
+    /**
+     * Calculates the file offset for a particular block.
+     * <p>
+     * @param block number
+     * @return the byte offset for this block in the file as a long
+     * @since 2.0
+     */
+    protected long calculateByteOffsetForBlockAsLong(int block)
+    {
+        return (long) block * blockSizeBytes;
+    }
+
+    /**
+     * The number of blocks needed.
+     * <p>
+     * @param data
+     * @return the number of blocks needed to store the byte array
+     */
+    protected int calculateTheNumberOfBlocksNeeded(byte[] data)
+    {
+        int dataLength = data.length;
+
+        int oneBlock = blockSizeBytes - HEADER_SIZE_BYTES;
+
+        // takes care of 0 = HEADER_SIZE_BYTES + blockSizeBytes
+        if (dataLength <= oneBlock)
+        {
+            return 1;
+        }
+
+        int dividend = dataLength / oneBlock;
+
+        if (dataLength % oneBlock != 0)
+        {
+            dividend++;
+        }
+        return dividend;
+    }
+
+    /**
+     * Returns the file length.
+     * <p>
+     * @return the size of the file.
+     * @throws IOException
+     */
+    protected long length()
+        throws IOException
+    {
+        return fc.size();
+    }
+
+    /**
+     * Closes the file.
+     * <p>
+     * @throws IOException
+     */
+    @Override
+    public void close()
+        throws IOException
+    {
+        this.numberOfBlocks.set(0);
+        this.emptyBlocks.clear();
+        fc.close();
+    }
+
+    /**
+     * Resets the file.
+     * <p>
+     * @throws IOException
+     */
+    protected synchronized void reset()
+        throws IOException
+    {
+        this.numberOfBlocks.set(0);
+        this.emptyBlocks.clear();
+        fc.truncate(0);
+        fc.force(true);
+    }
+
+    /**
+     * @return Returns the numberOfBlocks.
+     */
+    protected int getNumberOfBlocks()
+    {
+        return numberOfBlocks.get();
+    }
+
+    /**
+     * @return Returns the blockSizeBytes.
+     */
+    protected int getBlockSizeBytes()
+    {
+        return blockSizeBytes;
+    }
+
+    /**
+     * @return Returns the average size of the an element inserted.
+     */
+    protected long getAveragePutSizeBytes()
+    {
+        long count = this.putCount.get();
+
+        if (count == 0)
+        {
+            return 0;
+        }
+        return this.putBytes.get() / count;
+    }
+
+    /**
+     * @return Returns the number of empty blocks.
+     */
+    protected int getEmptyBlocks()
+    {
+        return this.emptyBlocks.size();
+    }
+
+    /**
+     * For debugging only.
+     * <p>
+     * @return String with details.
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        buf.append("\nBlock Disk ");
+        buf.append("\n  Filepath [" + filepath + "]");
+        buf.append("\n  NumberOfBlocks [" + this.numberOfBlocks.get() + "]");
+        buf.append("\n  BlockSizeBytes [" + this.blockSizeBytes + "]");
+        buf.append("\n  Put Bytes [" + this.putBytes + "]");
+        buf.append("\n  Put Count [" + this.putCount + "]");
+        buf.append("\n  Average Size [" + getAveragePutSizeBytes() + "]");
+        buf.append("\n  Empty Blocks [" + this.getEmptyBlocks() + "]");
+        try
+        {
+            buf.append("\n  Length [" + length() + "]");
+        }
+        catch (IOException e)
+        {
+            // swallow
+        }
+        return buf.toString();
+    }
+
+    /**
+     * This is used for debugging.
+     * <p>
+     * @return the file path.
+     */
+    protected String getFilePath()
+    {
+        return filepath;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCache.java
new file mode 100644
index 0000000..54e6919
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCache.java
@@ -0,0 +1,709 @@
+package org.apache.commons.jcs3.auxiliary.disk.block;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.stream.Collectors;
+
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.disk.AbstractDiskCache;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.engine.behavior.IRequireScheduler;
+import org.apache.commons.jcs3.engine.control.group.GroupAttrName;
+import org.apache.commons.jcs3.engine.control.group.GroupId;
+import org.apache.commons.jcs3.engine.stats.StatElement;
+import org.apache.commons.jcs3.engine.stats.Stats;
+import org.apache.commons.jcs3.engine.stats.behavior.IStatElement;
+import org.apache.commons.jcs3.engine.stats.behavior.IStats;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * There is one BlockDiskCache per region. It manages the key and data store.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class BlockDiskCache<K, V>
+    extends AbstractDiskCache<K, V>
+    implements IRequireScheduler
+{
+    /** The logger. */
+    private static final Log log = LogManager.getLog( BlockDiskCache.class );
+
+    /** The name to prefix all log messages with. */
+    private final String logCacheName;
+
+    /** The name of the file to store data. */
+    private final String fileName;
+
+    /** The data access object */
+    private BlockDisk dataFile;
+
+    /** Attributes governing the behavior of the block disk cache. */
+    private final BlockDiskCacheAttributes blockDiskCacheAttributes;
+
+    /** The root directory for keys and data. */
+    private final File rootDirectory;
+
+    /** Store, loads, and persists the keys */
+    private BlockDiskKeyStore<K> keyStore;
+
+    /**
+     * Use this lock to synchronize reads and writes to the underlying storage mechanism. We don't
+     * need a reentrant lock, since we only lock one level.
+     */
+    private final ReentrantReadWriteLock storageLock = new ReentrantReadWriteLock();
+
+    private ScheduledFuture<?> future;
+
+    /**
+     * Constructs the BlockDisk after setting up the root directory.
+     * <p>
+     * @param cacheAttributes
+     */
+    public BlockDiskCache( BlockDiskCacheAttributes cacheAttributes )
+    {
+        this( cacheAttributes, null );
+    }
+
+    /**
+     * Constructs the BlockDisk after setting up the root directory.
+     * <p>
+     * @param cacheAttributes
+     * @param elementSerializer used if supplied, the super's super will not set a null
+     */
+    public BlockDiskCache( BlockDiskCacheAttributes cacheAttributes, IElementSerializer elementSerializer )
+    {
+        super( cacheAttributes );
+        setElementSerializer( elementSerializer );
+
+        this.blockDiskCacheAttributes = cacheAttributes;
+        this.logCacheName = "Region [" + getCacheName() + "] ";
+
+        log.info("{0}: Constructing BlockDiskCache with attributes {1}", logCacheName, cacheAttributes );
+
+        // Make a clean file name
+        this.fileName = getCacheName().replaceAll("[^a-zA-Z0-9-_\\.]", "_");
+        this.rootDirectory = cacheAttributes.getDiskPath();
+
+        log.info("{0}: Cache file root directory: [{1}]", logCacheName, rootDirectory);
+
+        try
+        {
+            if ( this.blockDiskCacheAttributes.getBlockSizeBytes() > 0 )
+            {
+                this.dataFile = new BlockDisk( new File( rootDirectory, fileName + ".data" ),
+                                               this.blockDiskCacheAttributes.getBlockSizeBytes(),
+                                               getElementSerializer() );
+            }
+            else
+            {
+                this.dataFile = new BlockDisk( new File( rootDirectory, fileName + ".data" ),
+                                               getElementSerializer() );
+            }
+
+            keyStore = new BlockDiskKeyStore<>( this.blockDiskCacheAttributes, this );
+
+            boolean alright = verifyDisk();
+
+            if ( keyStore.size() == 0 || !alright )
+            {
+                this.reset();
+            }
+
+            // Initialization finished successfully, so set alive to true.
+            setAlive(true);
+            log.info("{0}: Block Disk Cache is alive.", logCacheName);
+        }
+        catch ( IOException e )
+        {
+            log.error("{0}: Failure initializing for fileName: {1} and root directory: {2}",
+                    logCacheName, fileName, rootDirectory, e);
+        }
+    }
+
+    /**
+     * @see org.apache.commons.jcs3.engine.behavior.IRequireScheduler#setScheduledExecutorService(java.util.concurrent.ScheduledExecutorService)
+     */
+    @Override
+    public void setScheduledExecutorService(ScheduledExecutorService scheduledExecutor)
+    {
+        // add this region to the persistence thread.
+        // TODO we might need to stagger this a bit.
+        if ( this.blockDiskCacheAttributes.getKeyPersistenceIntervalSeconds() > 0 )
+        {
+            future = scheduledExecutor.scheduleAtFixedRate(keyStore::saveKeys,
+                    this.blockDiskCacheAttributes.getKeyPersistenceIntervalSeconds(),
+                    this.blockDiskCacheAttributes.getKeyPersistenceIntervalSeconds(),
+                    TimeUnit.SECONDS);
+        }
+    }
+
+    /**
+     * We need to verify that the file on disk uses the same block size and that the file is the
+     * proper size.
+     * <p>
+     * @return true if it looks ok
+     */
+    protected boolean verifyDisk()
+    {
+        boolean alright = false;
+        // simply try to read a few. If it works, then the file is probably ok.
+        // TODO add more.
+
+        storageLock.readLock().lock();
+
+        try
+        {
+            this.keyStore.entrySet().stream()
+                .limit(100)
+                .forEach(entry -> {
+                    try
+                    {
+                        Object data = this.dataFile.read(entry.getValue());
+                        if ( data == null )
+                        {
+                            throw new IOException("Data is null");
+                        }
+                    }
+                    catch (IOException | ClassNotFoundException e)
+                    {
+                        throw new RuntimeException(logCacheName
+                                + " Couldn't find data for key [" + entry.getKey() + "]", e);
+                    }
+                });
+            alright = true;
+        }
+        catch ( Exception e )
+        {
+            log.warn("{0}: Problem verifying disk.", logCacheName, e);
+            alright = false;
+        }
+        finally
+        {
+            storageLock.readLock().unlock();
+        }
+
+        return alright;
+    }
+
+    /**
+     * Return the keys in this cache.
+     * <p>
+     * @see org.apache.commons.jcs3.auxiliary.disk.AbstractDiskCache#getKeySet()
+     */
+    @Override
+    public Set<K> getKeySet() throws IOException
+    {
+        HashSet<K> keys = new HashSet<>();
+
+        storageLock.readLock().lock();
+
+        try
+        {
+            keys.addAll(this.keyStore.keySet());
+        }
+        finally
+        {
+            storageLock.readLock().unlock();
+        }
+
+        return keys;
+    }
+
+    /**
+     * Gets matching items from the cache.
+     * <p>
+     * @param pattern
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache matching keys
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> processGetMatching( String pattern )
+    {
+        Set<K> keyArray = null;
+        storageLock.readLock().lock();
+        try
+        {
+            keyArray = new HashSet<>(keyStore.keySet());
+        }
+        finally
+        {
+            storageLock.readLock().unlock();
+        }
+
+        Set<K> matchingKeys = getKeyMatcher().getMatchingKeysFromArray( pattern, keyArray );
+
+        Map<K, ICacheElement<K, V>> elements = matchingKeys.stream()
+            .collect(Collectors.toMap(
+                    key -> key,
+                    key -> processGet( key ))).entrySet().stream()
+                .filter(entry -> entry.getValue() != null)
+                .collect(Collectors.toMap(
+                        entry -> entry.getKey(),
+                        entry -> entry.getValue()));
+
+        return elements;
+    }
+
+    /**
+     * Returns the number of keys.
+     * <p>
+     * (non-Javadoc)
+     * @see org.apache.commons.jcs3.auxiliary.disk.AbstractDiskCache#getSize()
+     */
+    @Override
+    public int getSize()
+    {
+        return this.keyStore.size();
+    }
+
+    /**
+     * Gets the ICacheElement&lt;K, V&gt; for the key if it is in the cache. The program flow is as follows:
+     * <ol>
+     * <li>Make sure the disk cache is alive.</li> <li>Get a read lock.</li> <li>See if the key is
+     * in the key store.</li> <li>If we found a key, ask the BlockDisk for the object at the
+     * blocks..</li> <li>Release the lock.</li>
+     * </ol>
+     * @param key
+     * @return ICacheElement
+     * @see org.apache.commons.jcs3.auxiliary.disk.AbstractDiskCache#get(Object)
+     */
+    @Override
+    protected ICacheElement<K, V> processGet( K key )
+    {
+        if ( !isAlive() )
+        {
+            log.debug("{0}: No longer alive so returning null for key = {1}", logCacheName, key );
+            return null;
+        }
+
+        log.debug("{0}: Trying to get from disk: {1}", logCacheName, key );
+
+        ICacheElement<K, V> object = null;
+
+
+        try
+        {
+            storageLock.readLock().lock();
+            try {
+                int[] ded = this.keyStore.get( key );
+                if ( ded != null )
+                {
+                    object = this.dataFile.read( ded );
+                }
+            } finally {
+                storageLock.readLock().unlock();
+            }
+
+        }
+        catch ( IOException ioe )
+        {
+            log.error("{0}: Failure getting from disk--IOException, key = {1}", logCacheName, key, ioe );
+            reset();
+        }
+        catch ( Exception e )
+        {
+            log.error("{0}: Failure getting from disk, key = {1}", logCacheName, key, e );
+        }
+        return object;
+    }
+
+    /**
+     * Writes an element to disk. The program flow is as follows:
+     * <ol>
+     * <li>Acquire write lock.</li> <li>See id an item exists for this key.</li> <li>If an item
+     * already exists, add its blocks to the remove list.</li> <li>Have the Block disk write the
+     * item.</li> <li>Create a descriptor and add it to the key map.</li> <li>Release the write
+     * lock.</li>
+     * </ol>
+     * @param element
+     * @see org.apache.commons.jcs3.auxiliary.disk.AbstractDiskCache#update(ICacheElement)
+     */
+    @Override
+    protected void processUpdate( ICacheElement<K, V> element )
+    {
+        if ( !isAlive() )
+        {
+            log.debug("{0}: No longer alive; aborting put of key = {1}",
+                    () -> logCacheName, () -> element.getKey());
+            return;
+        }
+
+        int[] old = null;
+
+        // make sure this only locks for one particular cache region
+        storageLock.writeLock().lock();
+
+        try
+        {
+            old = this.keyStore.get( element.getKey() );
+
+            if ( old != null )
+            {
+                this.dataFile.freeBlocks( old );
+            }
+
+            int[] blocks = this.dataFile.write( element );
+
+            this.keyStore.put( element.getKey(), blocks );
+
+            log.debug("{0}: Put to file [{1}] key [{2}]", () -> logCacheName,
+                    () -> fileName, () -> element.getKey());
+        }
+        catch ( IOException e )
+        {
+            log.error("{0}: Failure updating element, key: {1} old: {2}",
+                    logCacheName, element.getKey(), Arrays.toString(old), e);
+        }
+        finally
+        {
+            storageLock.writeLock().unlock();
+        }
+
+        log.debug("{0}: Storing element on disk, key: {1}", () -> logCacheName,
+                () -> element.getKey() );
+    }
+
+    /**
+     * Returns true if the removal was successful; or false if there is nothing to remove. Current
+     * implementation always result in a disk orphan.
+     * <p>
+     * @param key
+     * @return true if removed anything
+     * @see org.apache.commons.jcs3.auxiliary.disk.AbstractDiskCache#remove(Object)
+     */
+    @Override
+    protected boolean processRemove( K key )
+    {
+        if ( !isAlive() )
+        {
+            log.debug("{0}: No longer alive so returning false for key = {1}", logCacheName, key );
+            return false;
+        }
+
+        boolean reset = false;
+        boolean removed = false;
+
+        storageLock.writeLock().lock();
+
+        try
+        {
+            if (key instanceof String && key.toString().endsWith(NAME_COMPONENT_DELIMITER))
+            {
+                removed = performPartialKeyRemoval((String) key);
+            }
+            else if (key instanceof GroupAttrName && ((GroupAttrName<?>) key).attrName == null)
+            {
+                removed = performGroupRemoval(((GroupAttrName<?>) key).groupId);
+            }
+            else
+            {
+                removed = performSingleKeyRemoval(key);
+            }
+        }
+        catch ( Exception e )
+        {
+            log.error("{0}: Problem removing element.", logCacheName, e );
+            reset = true;
+        }
+        finally
+        {
+            storageLock.writeLock().unlock();
+        }
+
+        if ( reset )
+        {
+            reset();
+        }
+
+        return removed;
+    }
+
+    /**
+     * Remove all elements from the group. This does not use the iterator to remove. It builds a
+     * list of group elements and then removes them one by one.
+     * <p>
+     * This operates under a lock obtained in doRemove().
+     * <p>
+     *
+     * @param key
+     * @return true if an element was removed
+     */
+    private boolean performGroupRemoval(GroupId key)
+    {
+        // remove all keys of the same name group.
+        List<K> itemsToRemove = keyStore.keySet()
+                .stream()
+                .filter(k -> k instanceof GroupAttrName && ((GroupAttrName<?>) k).groupId.equals(key))
+                .collect(Collectors.toList());
+
+        // remove matches.
+        // Don't add to recycle bin here
+        // https://issues.apache.org/jira/browse/JCS-67
+        itemsToRemove.forEach(fullKey -> performSingleKeyRemoval(fullKey));
+        // TODO this needs to update the remove count separately
+
+        return !itemsToRemove.isEmpty();
+    }
+
+    /**
+     * Iterates over the keyset. Builds a list of matches. Removes all the keys in the list. Does
+     * not remove via the iterator, since the map impl may not support it.
+     * <p>
+     * This operates under a lock obtained in doRemove().
+     * <p>
+     *
+     * @param key
+     * @return true if there was a match
+     */
+    private boolean performPartialKeyRemoval(String key)
+    {
+        // remove all keys of the same name hierarchy.
+        List<K> itemsToRemove = keyStore.keySet()
+                .stream()
+                .filter(k -> k instanceof String && k.toString().startsWith(key))
+                .collect(Collectors.toList());
+
+        // remove matches.
+        // Don't add to recycle bin here
+        // https://issues.apache.org/jira/browse/JCS-67
+        itemsToRemove.forEach(fullKey -> performSingleKeyRemoval(fullKey));
+        // TODO this needs to update the remove count separately
+
+        return !itemsToRemove.isEmpty();
+    }
+
+
+	private boolean performSingleKeyRemoval(K key) {
+		boolean removed;
+		// remove single item.
+		int[] ded = this.keyStore.remove( key );
+		removed = ded != null;
+		if ( removed )
+		{
+		    this.dataFile.freeBlocks( ded );
+		}
+
+	    log.debug("{0}: Disk removal: Removed from key hash, key [{1}] removed = {2}",
+	            logCacheName, key, removed);
+		return removed;
+	}
+
+    /**
+     * Resets the keyfile, the disk file, and the memory key map.
+     * <p>
+     * @see org.apache.commons.jcs3.auxiliary.disk.AbstractDiskCache#removeAll()
+     */
+    @Override
+    protected void processRemoveAll()
+    {
+        reset();
+    }
+
+    /**
+     * Dispose of the disk cache in a background thread. Joins against this thread to put a cap on
+     * the disposal time.
+     * <p>
+     * TODO make dispose window configurable.
+     */
+    @Override
+    public void processDispose()
+    {
+        Thread t = new Thread(this::disposeInternal, "BlockDiskCache-DisposalThread" );
+        t.start();
+        // wait up to 60 seconds for dispose and then quit if not done.
+        try
+        {
+            t.join( 60 * 1000 );
+        }
+        catch ( InterruptedException ex )
+        {
+            log.error("{0}: Interrupted while waiting for disposal thread to finish.",
+                    logCacheName, ex );
+        }
+    }
+
+    /**
+     * Internal method that handles the disposal.
+     */
+    protected void disposeInternal()
+    {
+        if ( !isAlive() )
+        {
+            log.error("{0}: Not alive and dispose was called, filename: {1}", logCacheName, fileName);
+            return;
+        }
+        storageLock.writeLock().lock();
+        try
+        {
+            // Prevents any interaction with the cache while we're shutting down.
+            setAlive(false);
+            this.keyStore.saveKeys();
+
+            if (future != null)
+            {
+                future.cancel(true);
+            }
+
+            try
+            {
+                log.debug("{0}: Closing files, base filename: {1}", logCacheName, fileName );
+                dataFile.close();
+                // dataFile = null;
+
+                // TOD make a close
+                // keyFile.close();
+                // keyFile = null;
+            }
+            catch ( IOException e )
+            {
+                log.error("{0}: Failure closing files in dispose, filename: {1}",
+                        logCacheName, fileName, e );
+            }
+        }
+        finally
+        {
+            storageLock.writeLock().unlock();
+        }
+
+        log.info("{0}: Shutdown complete.", logCacheName);
+    }
+
+    /**
+     * Returns the attributes.
+     * <p>
+     * @see org.apache.commons.jcs3.auxiliary.AuxiliaryCache#getAuxiliaryCacheAttributes()
+     */
+    @Override
+    public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
+    {
+        return this.blockDiskCacheAttributes;
+    }
+
+    /**
+     * Reset effectively clears the disk cache, creating new files, recyclebins, and keymaps.
+     * <p>
+     * It can be used to handle errors by last resort, force content update, or removeall.
+     */
+    private void reset()
+    {
+        log.info("{0}: Resetting cache", logCacheName);
+
+        try
+        {
+            storageLock.writeLock().lock();
+
+            this.keyStore.reset();
+
+            if ( dataFile != null )
+            {
+                dataFile.reset();
+            }
+        }
+        catch ( IOException e )
+        {
+            log.error("{0}: Failure resetting state", logCacheName, e );
+        }
+        finally
+        {
+            storageLock.writeLock().unlock();
+        }
+    }
+
+    /**
+     * Add these blocks to the emptyBlock list.
+     * <p>
+     * @param blocksToFree
+     */
+    protected void freeBlocks( int[] blocksToFree )
+    {
+        this.dataFile.freeBlocks( blocksToFree );
+    }
+
+    /**
+     * Returns info about the disk cache.
+     * <p>
+     * @see org.apache.commons.jcs3.auxiliary.AuxiliaryCache#getStatistics()
+     */
+    @Override
+    public IStats getStatistics()
+    {
+        IStats stats = new Stats();
+        stats.setTypeName( "Block Disk Cache" );
+
+        ArrayList<IStatElement<?>> elems = new ArrayList<>();
+
+        elems.add(new StatElement<>( "Is Alive", Boolean.valueOf(isAlive()) ) );
+        elems.add(new StatElement<>( "Key Map Size", Integer.valueOf(this.keyStore.size()) ) );
+
+        if (this.dataFile != null)
+        {
+            try
+            {
+                elems.add(new StatElement<>( "Data File Length", Long.valueOf(this.dataFile.length()) ) );
+            }
+            catch ( IOException e )
+            {
+                log.error( e );
+            }
+
+            elems.add(new StatElement<>( "Block Size Bytes",
+                    Integer.valueOf(this.dataFile.getBlockSizeBytes()) ) );
+            elems.add(new StatElement<>( "Number Of Blocks",
+                    Integer.valueOf(this.dataFile.getNumberOfBlocks()) ) );
+            elems.add(new StatElement<>( "Average Put Size Bytes",
+                    Long.valueOf(this.dataFile.getAveragePutSizeBytes()) ) );
+            elems.add(new StatElement<>( "Empty Blocks",
+                    Integer.valueOf(this.dataFile.getEmptyBlocks()) ) );
+        }
+
+        // get the stats from the super too
+        IStats sStats = super.getStatistics();
+        elems.addAll(sStats.getStatElements());
+
+        stats.setStatElements( elems );
+
+        return stats;
+    }
+
+    /**
+     * This is used by the event logging.
+     * <p>
+     * @return the location of the disk, either path or ip.
+     */
+    @Override
+    protected String getDiskLocation()
+    {
+        return dataFile.getFilePath();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCacheAttributes.java
new file mode 100644
index 0000000..6df7d40
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCacheAttributes.java
@@ -0,0 +1,118 @@
+package org.apache.commons.jcs3.auxiliary.disk.block;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.auxiliary.disk.AbstractDiskCacheAttributes;
+
+/**
+ * This holds attributes for Block Disk Cache configuration.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class BlockDiskCacheAttributes
+    extends AbstractDiskCacheAttributes
+{
+    /** Don't change */
+    private static final long serialVersionUID = 6568840097657265989L;
+
+    /** The size per block in bytes. */
+    private int blockSizeBytes;
+
+    /** Maximum number of keys to be kept in memory */
+    private static final int DEFAULT_MAX_KEY_SIZE = 5000;
+
+    /** -1 means no limit. */
+    private int maxKeySize = DEFAULT_MAX_KEY_SIZE;
+
+    /** How often should we persist the keys. */
+    private static final long DEFAULT_KEY_PERSISTENCE_INTERVAL_SECONDS = 5 * 60;
+
+    /** The keys will be persisted at this interval.  -1 mean never. */
+    private long keyPersistenceIntervalSeconds = DEFAULT_KEY_PERSISTENCE_INTERVAL_SECONDS;
+
+    /**
+     * The size of the blocks. All blocks are the same size.
+     * <p>
+     * @param blockSizeBytes The blockSizeBytes to set.
+     */
+    public void setBlockSizeBytes( int blockSizeBytes )
+    {
+        this.blockSizeBytes = blockSizeBytes;
+    }
+
+    /**
+     * @return Returns the blockSizeBytes.
+     */
+    public int getBlockSizeBytes()
+    {
+        return blockSizeBytes;
+    }
+
+    /**
+     * @param maxKeySize The maxKeySize to set.
+     */
+    public void setMaxKeySize( int maxKeySize )
+    {
+        this.maxKeySize = maxKeySize;
+    }
+
+    /**
+     * @return Returns the maxKeySize.
+     */
+    public int getMaxKeySize()
+    {
+        return maxKeySize;
+    }
+
+    /**
+     * @param keyPersistenceIntervalSeconds The keyPersistenceIntervalSeconds to set.
+     */
+    public void setKeyPersistenceIntervalSeconds( long keyPersistenceIntervalSeconds )
+    {
+        this.keyPersistenceIntervalSeconds = keyPersistenceIntervalSeconds;
+    }
+
+    /**
+     * @return Returns the keyPersistenceIntervalSeconds.
+     */
+    public long getKeyPersistenceIntervalSeconds()
+    {
+        return keyPersistenceIntervalSeconds;
+    }
+
+    /**
+     * Write out the values for debugging purposes.
+     * <p>
+     * @return String
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder str = new StringBuilder();
+        str.append( "\nBlockDiskAttributes " );
+        str.append( "\n DiskPath [" + this.getDiskPath() + "]" );
+        str.append( "\n MaxKeySize [" + this.getMaxKeySize() + "]" );
+        str.append( "\n MaxPurgatorySize [" + this.getMaxPurgatorySize() + "]" );
+        str.append( "\n BlockSizeBytes [" + this.getBlockSizeBytes() + "]" );
+        str.append( "\n KeyPersistenceIntervalSeconds [" + this.getKeyPersistenceIntervalSeconds() + "]" );
+        str.append( "\n DiskLimitType [" + this.getDiskLimitType() + "]" );
+        return str.toString();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCacheFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCacheFactory.java
new file mode 100644
index 0000000..8932375
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCacheFactory.java
@@ -0,0 +1,62 @@
+package org.apache.commons.jcs3.auxiliary.disk.block;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCacheFactory;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * Creates disk cache instances.
+ */
+public class BlockDiskCacheFactory
+    extends AbstractAuxiliaryCacheFactory
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog( BlockDiskCacheFactory.class );
+
+    /**
+     * Create an instance of the BlockDiskCache.
+     * <p>
+     * @param iaca the cache attributes for this cache
+     * @param cacheMgr This allows auxiliaries to reference the manager without assuming that it is
+     *            a singleton. This will allow JCS to be a non-singleton. Also, it makes it easier
+     *            to test.
+     * @param cacheEventLogger
+     * @param elementSerializer
+     * @return BlockDiskCache
+     */
+    @Override
+    public <K, V> BlockDiskCache<K, V> createCache( AuxiliaryCacheAttributes iaca, ICompositeCacheManager cacheMgr,
+                                       ICacheEventLogger cacheEventLogger, IElementSerializer elementSerializer )
+    {
+        BlockDiskCacheAttributes idca = (BlockDiskCacheAttributes) iaca;
+        log.debug("Creating DiskCache for attributes = {0}", idca);
+
+        BlockDiskCache<K, V> cache = new BlockDiskCache<>( idca, elementSerializer );
+        cache.setCacheEventLogger( cacheEventLogger );
+
+        return cache;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskElementDescriptor.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskElementDescriptor.java
new file mode 100644
index 0000000..3dedb97
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskElementDescriptor.java
@@ -0,0 +1,132 @@
+package org.apache.commons.jcs3.auxiliary.disk.block;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.io.Serializable;
+
+/**
+ * This represents an element on disk. This is used when we persist the keys. We only store the
+ * block addresses in memory. We don't need the length here, since all the blocks are the same size
+ * receyle bin.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class BlockDiskElementDescriptor<K>
+    implements Serializable, Externalizable
+{
+    /** Don't change */
+    private static final long serialVersionUID = -1400659301208101411L;
+
+    /** The key */
+    private K key;
+
+    /** The array of block numbers */
+    private int[] blocks;
+
+    /**
+     * @param key The key to set.
+     */
+    public void setKey( K key )
+    {
+        this.key = key;
+    }
+
+    /**
+     * @return Returns the key.
+     */
+    public K getKey()
+    {
+        return key;
+    }
+
+    /**
+     * @param blocks The blocks to set.
+     */
+    public void setBlocks( int[] blocks )
+    {
+        this.blocks = blocks;
+    }
+
+    /**
+     * This holds the block numbers. An item my be dispersed between multiple blocks.
+     * <p>
+     * @return Returns the blocks.
+     */
+    public int[] getBlocks()
+    {
+        return blocks;
+    }
+
+    /**
+     * For debugging.
+     * <p>
+     * @return Info on the descriptor.
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        buf.append( "\nBlockDiskElementDescriptor" );
+        buf.append( "\n key [" + this.getKey() + "]" );
+        buf.append( "\n blocks [" );
+        if ( this.getBlocks() != null )
+        {
+            for ( int i = 0; i < blocks.length; i++ )
+            {
+                buf.append( this.getBlocks()[i] );
+            }
+        }
+        buf.append( "]" );
+        return buf.toString();
+    }
+
+    /**
+     * Saves on reflection.
+     * <p>
+     * (non-Javadoc)
+     * @see java.io.Externalizable#readExternal(java.io.ObjectInput)
+     */
+    @Override
+    @SuppressWarnings("unchecked") // Need cast to K
+    public void readExternal( ObjectInput input )
+        throws IOException, ClassNotFoundException
+    {
+        this.key = (K) input.readObject();
+        this.blocks = (int[]) input.readObject();
+    }
+
+    /**
+     * Saves on reflection.
+     * <p>
+     * (non-Javadoc)
+     * @see java.io.Externalizable#writeExternal(java.io.ObjectOutput)
+     */
+    @Override
+    public void writeExternal( ObjectOutput output )
+        throws IOException
+    {
+        output.writeObject( this.key );
+        output.writeObject( this.blocks );
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskKeyStore.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskKeyStore.java
new file mode 100644
index 0000000..f405eeb
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskKeyStore.java
@@ -0,0 +1,550 @@
+package org.apache.commons.jcs3.auxiliary.disk.block;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.commons.jcs3.auxiliary.disk.behavior.IDiskCacheAttributes.DiskLimitType;
+import org.apache.commons.jcs3.io.ObjectInputStreamClassLoaderAware;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.struct.AbstractLRUMap;
+import org.apache.commons.jcs3.utils.struct.LRUMap;
+import org.apache.commons.jcs3.utils.timing.ElapsedTimer;
+
+/**
+ * This is responsible for storing the keys.
+ * <p>
+ *
+ * @author Aaron Smuts
+ */
+public class BlockDiskKeyStore<K>
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog(BlockDiskKeyStore.class);
+
+    /** Attributes governing the behavior of the block disk cache. */
+    private final BlockDiskCacheAttributes blockDiskCacheAttributes;
+
+    /** The key to block map */
+    private Map<K, int[]> keyHash;
+
+    /** The file where we persist the keys */
+    private final File keyFile;
+
+    /** The name to prefix log messages with. */
+    protected final String logCacheName;
+
+    /** Name of the file where we persist the keys */
+    private final String fileName;
+
+    /** The maximum number of keys to store in memory */
+    private final int maxKeySize;
+
+    /**
+     * we need this so we can communicate free blocks to the data store when
+     * keys fall off the LRU
+     */
+    protected final BlockDiskCache<K, ?> blockDiskCache;
+
+    private DiskLimitType diskLimitType = DiskLimitType.COUNT;
+
+    private final int blockSize;
+
+    /**
+     * Set the configuration options.
+     * <p>
+     *
+     * @param cacheAttributes
+     * @param blockDiskCache
+     *            used for freeing
+     */
+    public BlockDiskKeyStore(BlockDiskCacheAttributes cacheAttributes, BlockDiskCache<K, ?> blockDiskCache)
+    {
+        this.blockDiskCacheAttributes = cacheAttributes;
+        this.logCacheName = "Region [" + this.blockDiskCacheAttributes.getCacheName() + "] ";
+        this.fileName = this.blockDiskCacheAttributes.getCacheName();
+        this.maxKeySize = cacheAttributes.getMaxKeySize();
+        this.blockDiskCache = blockDiskCache;
+        this.diskLimitType = cacheAttributes.getDiskLimitType();
+        this.blockSize = cacheAttributes.getBlockSizeBytes();
+
+        File rootDirectory = cacheAttributes.getDiskPath();
+
+        log.info("{0}: Cache file root directory [{1}]", logCacheName, rootDirectory);
+
+        this.keyFile = new File(rootDirectory, fileName + ".key");
+
+        log.info("{0}: Key File [{1}]", logCacheName, this.keyFile.getAbsolutePath());
+
+        if (keyFile.length() > 0)
+        {
+            loadKeys();
+            if (!verify())
+            {
+                log.warn("{0}: Key File is invalid. Resetting file.", logCacheName);
+                initKeyMap();
+                reset();
+            }
+        }
+        else
+        {
+            initKeyMap();
+        }
+    }
+
+    /**
+     * Saves key file to disk. This gets the LRUMap entry set and write the
+     * entries out one by one after putting them in a wrapper.
+     */
+    protected void saveKeys()
+    {
+        try
+        {
+            ElapsedTimer timer = new ElapsedTimer();
+            int numKeys = keyHash.size();
+            log.info("{0}: Saving keys to [{1}], key count [{2}]", () -> logCacheName,
+                    () -> this.keyFile.getAbsolutePath(), () -> numKeys);
+
+            synchronized (keyFile)
+            {
+                FileOutputStream fos = new FileOutputStream(keyFile);
+                BufferedOutputStream bos = new BufferedOutputStream(fos, 65536);
+
+                try (ObjectOutputStream oos = new ObjectOutputStream(bos))
+                {
+                    if (!verify())
+                    {
+                        throw new IOException("Inconsistent key file");
+                    }
+                    // don't need to synchronize, since the underlying
+                    // collection makes a copy
+                    for (Map.Entry<K, int[]> entry : keyHash.entrySet())
+                    {
+                        BlockDiskElementDescriptor<K> descriptor = new BlockDiskElementDescriptor<>();
+                        descriptor.setKey(entry.getKey());
+                        descriptor.setBlocks(entry.getValue());
+                        // stream these out in the loop.
+                        oos.writeUnshared(descriptor);
+                    }
+                }
+            }
+
+            log.info("{0}: Finished saving keys. It took {1} to store {2} keys. Key file length [{3}]",
+                    () -> logCacheName, () -> timer.getElapsedTimeString(), () -> numKeys,
+                    () -> keyFile.length());
+        }
+        catch (IOException e)
+        {
+            log.error("{0}: Problem storing keys.", logCacheName, e);
+        }
+    }
+
+    /**
+     * Resets the file and creates a new key map.
+     */
+    protected void reset()
+    {
+        synchronized (keyFile)
+        {
+            clearMemoryMap();
+            saveKeys();
+        }
+    }
+
+    /**
+     * This is mainly used for testing. It leave the disk in tact, and just
+     * clears memory.
+     */
+    protected void clearMemoryMap()
+    {
+        this.keyHash.clear();
+    }
+
+    /**
+     * Create the map for keys that contain the index position on disk.
+     */
+    private void initKeyMap()
+    {
+        keyHash = null;
+        if (maxKeySize >= 0)
+        {
+            if (this.diskLimitType == DiskLimitType.SIZE)
+            {
+                keyHash = new LRUMapSizeLimited(maxKeySize);
+            }
+            else
+            {
+                keyHash = new LRUMapCountLimited(maxKeySize);
+            }
+            log.info("{0}: Set maxKeySize to: \"{1}\"", logCacheName, maxKeySize);
+        }
+        else
+        {
+            // If no max size, use a plain map for memory and processing
+            // efficiency.
+            keyHash = new HashMap<>();
+            // keyHash = Collections.synchronizedMap( new HashMap() );
+            log.info("{0}: Set maxKeySize to unlimited", logCacheName);
+        }
+    }
+
+    /**
+     * Loads the keys from the .key file. The keys are stored individually on
+     * disk. They are added one by one to an LRUMap..
+     */
+    protected void loadKeys()
+    {
+        log.info("{0}: Loading keys for {1}", () -> logCacheName, () -> keyFile.toString());
+
+        try
+        {
+            // create a key map to use.
+            initKeyMap();
+
+            HashMap<K, int[]> keys = new HashMap<>();
+
+            synchronized (keyFile)
+            {
+                FileInputStream fis = new FileInputStream(keyFile);
+                BufferedInputStream bis = new BufferedInputStream(fis, 65536);
+
+                try (ObjectInputStream ois = new ObjectInputStreamClassLoaderAware(bis, null))
+                {
+                    while (true)
+                    {
+                        @SuppressWarnings("unchecked")
+                        // Need to cast from Object
+                        BlockDiskElementDescriptor<K> descriptor = (BlockDiskElementDescriptor<K>) ois.readObject();
+                        if (descriptor != null)
+                        {
+                            keys.put(descriptor.getKey(), descriptor.getBlocks());
+                        }
+                    }
+                }
+                catch (EOFException eof)
+                {
+                    // nothing
+                }
+            }
+
+            if (!keys.isEmpty())
+            {
+                keyHash.putAll(keys);
+
+                log.debug("{0}: Found {1} in keys file.", logCacheName, keys.size());
+                log.info("{0}: Loaded keys from [{1}], key count: {2}; up to {3} will be available.",
+                        () -> logCacheName, () -> fileName, () -> keyHash.size(),
+                        () -> maxKeySize);
+            }
+        }
+        catch (Exception e)
+        {
+            log.error("{0}: Problem loading keys for file {1}", logCacheName, fileName, e);
+        }
+    }
+
+    /**
+     * Gets the entry set.
+     * <p>
+     *
+     * @return entry set.
+     */
+    public Set<Map.Entry<K, int[]>> entrySet()
+    {
+        return this.keyHash.entrySet();
+    }
+
+    /**
+     * Gets the key set.
+     * <p>
+     *
+     * @return key set.
+     */
+    public Set<K> keySet()
+    {
+        return this.keyHash.keySet();
+    }
+
+    /**
+     * Gets the size of the key hash.
+     * <p>
+     *
+     * @return the number of keys.
+     */
+    public int size()
+    {
+        return this.keyHash.size();
+    }
+
+    /**
+     * gets the object for the key.
+     * <p>
+     *
+     * @param key
+     * @return Object
+     */
+    public int[] get(K key)
+    {
+        return this.keyHash.get(key);
+    }
+
+    /**
+     * Puts a int[] in the keyStore.
+     * <p>
+     *
+     * @param key
+     * @param value
+     */
+    public void put(K key, int[] value)
+    {
+        this.keyHash.put(key, value);
+    }
+
+    /**
+     * Remove by key.
+     * <p>
+     *
+     * @param key
+     * @return BlockDiskElementDescriptor if it was present, else null
+     */
+    public int[] remove(K key)
+    {
+        return this.keyHash.remove(key);
+    }
+
+    /**
+     * Verify key store integrity
+     *
+     * @return true if key store is valid
+     */
+    private boolean verify()
+    {
+        Map<Integer, Set<K>> blockAllocationMap = new TreeMap<>();
+        for (Entry<K, int[]> e : keyHash.entrySet())
+        {
+            for (int block : e.getValue())
+            {
+                Set<K> keys = blockAllocationMap.get(block);
+                if (keys == null)
+                {
+                    keys = new HashSet<>();
+                    blockAllocationMap.put(block, keys);
+                }
+                else if (!log.isTraceEnabled())
+                {
+                    // keys are not null, and no debug - fail fast
+                    return false;
+                }
+                keys.add(e.getKey());
+            }
+        }
+        boolean ok = true;
+        if (log.isTraceEnabled())
+        {
+            for (Entry<Integer, Set<K>> e : blockAllocationMap.entrySet())
+            {
+                log.trace("Block {0}: {1}", e.getKey(), e.getValue());
+                if (e.getValue().size() > 1)
+                {
+                    ok = false;
+                }
+            }
+            return ok;
+        }
+        else
+        {
+            return ok;
+        }
+    }
+
+    /**
+     * Class for recycling and lru. This implements the LRU size overflow
+     * callback, so we can mark the blocks as free.
+     */
+    public class LRUMapSizeLimited extends AbstractLRUMap<K, int[]>
+    {
+        /**
+         * <code>tag</code> tells us which map we are working on.
+         */
+        public final static String TAG = "orig-lru-size";
+
+        // size of the content in kB
+        private AtomicInteger contentSize;
+        private int maxSize;
+
+        /**
+         * Default
+         */
+        public LRUMapSizeLimited()
+        {
+            this(-1);
+        }
+
+        /**
+         * @param maxSize
+         *            maximum cache size in kB
+         */
+        public LRUMapSizeLimited(int maxSize)
+        {
+            super();
+            this.maxSize = maxSize;
+            this.contentSize = new AtomicInteger(0);
+        }
+
+        // keep the content size in kB, so 2^31 kB is reasonable value
+        private void subLengthFromCacheSize(int[] value)
+        {
+            contentSize.addAndGet(value.length * blockSize / -1024 - 1);
+        }
+
+        // keep the content size in kB, so 2^31 kB is reasonable value
+        private void addLengthToCacheSize(int[] value)
+        {
+            contentSize.addAndGet(value.length * blockSize / 1024 + 1);
+        }
+
+        @Override
+        public int[] put(K key, int[] value)
+        {
+            int[] oldValue = null;
+
+            try
+            {
+                oldValue = super.put(key, value);
+            }
+            finally
+            {
+                if (value != null)
+                {
+                    addLengthToCacheSize(value);
+                }
+                if (oldValue != null)
+                {
+                    subLengthFromCacheSize(oldValue);
+                }
+            }
+
+            return oldValue;
+        }
+
+        @Override
+        public int[] remove(Object key)
+        {
+            int[] value = null;
+
+            try
+            {
+                value = super.remove(key);
+                return value;
+            }
+            finally
+            {
+                if (value != null)
+                {
+                    subLengthFromCacheSize(value);
+                }
+            }
+        }
+
+        /**
+         * This is called when the may key size is reached. The least recently
+         * used item will be passed here. We will store the position and size of
+         * the spot on disk in the recycle bin.
+         * <p>
+         *
+         * @param key
+         * @param value
+         */
+        @Override
+        protected void processRemovedLRU(K key, int[] value)
+        {
+            blockDiskCache.freeBlocks(value);
+            if (log.isDebugEnabled())
+            {
+                log.debug("{0}: Removing key: [{1}] from key store.", logCacheName, key);
+                log.debug("{0}: Key store size: [{1}].", logCacheName, super.size());
+            }
+
+            if (value != null)
+            {
+                subLengthFromCacheSize(value);
+            }
+        }
+
+        @Override
+        protected boolean shouldRemove()
+        {
+            return maxSize > 0 && contentSize.get() > maxSize && this.size() > 1;
+        }
+    }
+
+    /**
+     * Class for recycling and lru. This implements the LRU overflow callback,
+     * so we can mark the blocks as free.
+     */
+    public class LRUMapCountLimited extends LRUMap<K, int[]>
+    {
+        /**
+         * <code>tag</code> tells us which map we are working on.
+         */
+        public final static String TAG = "orig-lru-count";
+
+        public LRUMapCountLimited(int maxKeySize)
+        {
+            super(maxKeySize);
+        }
+
+        /**
+         * This is called when the may key size is reached. The least recently
+         * used item will be passed here. We will store the position and size of
+         * the spot on disk in the recycle bin.
+         * <p>
+         *
+         * @param key
+         * @param value
+         */
+        @Override
+        protected void processRemovedLRU(K key, int[] value)
+        {
+            blockDiskCache.freeBlocks(value);
+            if (log.isDebugEnabled())
+            {
+                log.debug("{0}: Removing key: [{1}] from key store.", logCacheName, key);
+                log.debug("{0}: Key store size: [{1}].", logCacheName, super.size());
+            }
+        }
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDisk.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDisk.java
new file mode 100644
index 0000000..47df0fd
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDisk.java
@@ -0,0 +1,279 @@
+package org.apache.commons.jcs3.auxiliary.disk.indexed;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.file.StandardOpenOption;
+
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/** Provides thread safe access to the underlying random access file. */
+public class IndexedDisk implements AutoCloseable
+{
+    /** The size of the header that indicates the amount of data stored in an occupied block. */
+    public static final byte HEADER_SIZE_BYTES = 4;
+
+    /** The serializer. */
+    private final IElementSerializer elementSerializer;
+
+    /** The logger */
+    private static final Log log = LogManager.getLog(IndexedDisk.class);
+
+    /** The path to the log directory. */
+    private final String filepath;
+
+    /** The data file. */
+    private final FileChannel fc;
+
+    /**
+     * Constructor for the Disk object
+     * <p>
+     * @param file
+     * @param elementSerializer
+     * @throws IOException
+     */
+    public IndexedDisk(File file, IElementSerializer elementSerializer)
+        throws IOException
+    {
+        this.filepath = file.getAbsolutePath();
+        this.elementSerializer = elementSerializer;
+        this.fc = FileChannel.open(file.toPath(),
+                StandardOpenOption.CREATE,
+                StandardOpenOption.READ,
+                StandardOpenOption.WRITE);
+    }
+
+    /**
+     * This reads an object from the given starting position on the file.
+     * <p>
+     * The first four bytes of the record should tell us how long it is. The data is read into a byte
+     * array and then an object is constructed from the byte array.
+     * <p>
+     * @return Serializable
+     * @param ded
+     * @throws IOException
+     * @throws ClassNotFoundException
+     */
+    protected <T> T readObject(IndexedDiskElementDescriptor ded)
+        throws IOException, ClassNotFoundException
+    {
+        String message = null;
+        boolean corrupted = false;
+        long fileLength = fc.size();
+        if (ded.pos > fileLength)
+        {
+            corrupted = true;
+            message = "Record " + ded + " starts past EOF.";
+        }
+        else
+        {
+            ByteBuffer datalength = ByteBuffer.allocate(HEADER_SIZE_BYTES);
+            fc.read(datalength, ded.pos);
+            datalength.flip();
+            int datalen = datalength.getInt();
+            if (ded.len != datalen)
+            {
+                corrupted = true;
+                message = "Record " + ded + " does not match data length on disk (" + datalen + ")";
+            }
+            else if (ded.pos + ded.len > fileLength)
+            {
+                corrupted = true;
+                message = "Record " + ded + " exceeds file length.";
+            }
+        }
+
+        if (corrupted)
+        {
+            log.warn("\n The file is corrupt: \n {0}", message);
+            throw new IOException("The File Is Corrupt, need to reset");
+        }
+
+        ByteBuffer data = ByteBuffer.allocate(ded.len);
+        fc.read(data, ded.pos + HEADER_SIZE_BYTES);
+        data.flip();
+
+        return elementSerializer.deSerialize(data.array(), null);
+    }
+
+    /**
+     * Moves the data stored from one position to another. The descriptor's position is updated.
+     * <p>
+     * @param ded
+     * @param newPosition
+     * @throws IOException
+     */
+    protected void move(final IndexedDiskElementDescriptor ded, final long newPosition)
+        throws IOException
+    {
+        ByteBuffer datalength = ByteBuffer.allocate(HEADER_SIZE_BYTES);
+        fc.read(datalength, ded.pos);
+        datalength.flip();
+        int length = datalength.getInt();
+
+        if (length != ded.len)
+        {
+            throw new IOException("Mismatched memory and disk length (" + length + ") for " + ded);
+        }
+
+        // TODO: more checks?
+
+        long readPos = ded.pos;
+        long writePos = newPosition;
+
+        // header len + data len
+        int remaining = HEADER_SIZE_BYTES + length;
+        ByteBuffer buffer = ByteBuffer.allocate(16384);
+
+        while (remaining > 0)
+        {
+            // chunk it
+            int chunkSize = Math.min(remaining, buffer.capacity());
+            buffer.limit(chunkSize);
+            fc.read(buffer, readPos);
+            buffer.flip();
+            fc.write(buffer, writePos);
+            buffer.clear();
+
+            writePos += chunkSize;
+            readPos += chunkSize;
+            remaining -= chunkSize;
+        }
+
+        ded.pos = newPosition;
+    }
+
+    /**
+     * Writes the given byte array to the Disk at the specified position.
+     * <p>
+     * @param data
+     * @param ded
+     * @return true if we wrote successfully
+     * @throws IOException
+     */
+    protected boolean write(IndexedDiskElementDescriptor ded, byte[] data)
+        throws IOException
+    {
+        long pos = ded.pos;
+        if (log.isTraceEnabled())
+        {
+            log.trace("write> pos={0}", pos);
+            log.trace("{0} -- data.length = {1}", fc, data.length);
+        }
+
+        if (data.length != ded.len)
+        {
+            throw new IOException("Mismatched descriptor and data lengths");
+        }
+
+        ByteBuffer headerBuffer = ByteBuffer.allocate(HEADER_SIZE_BYTES);
+        headerBuffer.putInt(data.length);
+        // write the header
+        headerBuffer.flip();
+        int written = fc.write(headerBuffer, pos);
+        assert written == HEADER_SIZE_BYTES;
+
+        //write the data
+        ByteBuffer dataBuffer = ByteBuffer.wrap(data);
+        written = fc.write(dataBuffer, pos + HEADER_SIZE_BYTES);
+
+        return written == data.length;
+    }
+
+    /**
+     * Serializes the object and write it out to the given position.
+     * <p>
+     * TODO: make this take a ded as well.
+     * @param obj
+     * @param pos
+     * @throws IOException
+     */
+    protected <T> void writeObject(T obj, long pos)
+        throws IOException
+    {
+        byte[] data = elementSerializer.serialize(obj);
+        write(new IndexedDiskElementDescriptor(pos, data.length), data);
+    }
+
+    /**
+     * Returns the raf length.
+     * <p>
+     * @return the length of the file.
+     * @throws IOException
+     */
+    protected long length()
+        throws IOException
+    {
+        return fc.size();
+    }
+
+    /**
+     * Closes the raf.
+     * <p>
+     * @throws IOException
+     */
+    @Override
+    public void close()
+        throws IOException
+    {
+        fc.close();
+    }
+
+    /**
+     * Sets the raf to empty.
+     * <p>
+     * @throws IOException
+     */
+    protected synchronized void reset()
+        throws IOException
+    {
+        log.debug("Resetting Indexed File [{0}]", filepath);
+        fc.truncate(0);
+        fc.force(true);
+    }
+
+    /**
+     * Truncates the file to a given length.
+     * <p>
+     * @param length the new length of the file
+     * @throws IOException
+     */
+    protected void truncate(long length)
+        throws IOException
+    {
+        log.info("Truncating file [{0}] to {1}", filepath, length);
+        fc.truncate(length);
+    }
+
+    /**
+     * This is used for debugging.
+     * <p>
+     * @return the file path.
+     */
+    protected String getFilePath()
+    {
+        return filepath;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCache.java
new file mode 100644
index 0000000..66d5f2b
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCache.java
@@ -0,0 +1,1680 @@
+package org.apache.commons.jcs3.auxiliary.disk.indexed;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Serializable;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.disk.AbstractDiskCache;
+import org.apache.commons.jcs3.auxiliary.disk.behavior.IDiskCacheAttributes.DiskLimitType;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.engine.control.group.GroupAttrName;
+import org.apache.commons.jcs3.engine.control.group.GroupId;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEvent;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+import org.apache.commons.jcs3.engine.stats.StatElement;
+import org.apache.commons.jcs3.engine.stats.Stats;
+import org.apache.commons.jcs3.engine.stats.behavior.IStatElement;
+import org.apache.commons.jcs3.engine.stats.behavior.IStats;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.struct.AbstractLRUMap;
+import org.apache.commons.jcs3.utils.struct.LRUMap;
+import org.apache.commons.jcs3.utils.timing.ElapsedTimer;
+
+/**
+ * Disk cache that uses a RandomAccessFile with keys stored in memory. The maximum number of keys
+ * stored in memory is configurable. The disk cache tries to recycle spots on disk to limit file
+ * expansion.
+ */
+public class IndexedDiskCache<K, V> extends AbstractDiskCache<K, V>
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog(IndexedDiskCache.class);
+
+    /** Cache name used in log messages */
+    protected final String logCacheName;
+
+    /** The name of the file where the data is stored */
+    private final String fileName;
+
+    /** The IndexedDisk manages reads and writes to the data file. */
+    private IndexedDisk dataFile;
+
+    /** The IndexedDisk manages reads and writes to the key file. */
+    private IndexedDisk keyFile;
+
+    /** Map containing the keys and disk offsets. */
+    private final Map<K, IndexedDiskElementDescriptor> keyHash;
+
+    /** The maximum number of keys that we will keep in memory. */
+    private final int maxKeySize;
+
+    /** A handle on the data file. */
+    private File rafDir;
+
+    /** Should we keep adding to the recycle bin. False during optimization. */
+    private boolean doRecycle = true;
+
+    /** Should we optimize real time */
+    private boolean isRealTimeOptimizationEnabled = true;
+
+    /** Should we optimize on shutdown. */
+    private boolean isShutdownOptimizationEnabled = true;
+
+    /** are we currently optimizing the files */
+    private boolean isOptimizing = false;
+
+    /** The number of times the file has been optimized. */
+    private int timesOptimized = 0;
+
+    /** The thread optimizing the file. */
+    private volatile Thread currentOptimizationThread;
+
+    /** used for counting the number of requests */
+    private int removeCount = 0;
+
+    /** Should we queue puts. True when optimizing. We write the queue post optimization. */
+    private boolean queueInput = false;
+
+    /** list where puts made during optimization are made */
+    private final ConcurrentSkipListSet<IndexedDiskElementDescriptor> queuedPutList;
+
+    /** RECYLCE BIN -- array of empty spots */
+    private final ConcurrentSkipListSet<IndexedDiskElementDescriptor> recycle;
+
+    /** User configurable parameters */
+    private final IndexedDiskCacheAttributes cattr;
+
+    /** How many slots have we recycled. */
+    private int recycleCnt = 0;
+
+    /** How many items were there on startup. */
+    private int startupSize = 0;
+
+    /** the number of bytes free on disk. */
+    private final AtomicLong bytesFree = new AtomicLong(0);
+
+    /** mode we are working on (size or count limited **/
+    private DiskLimitType diskLimitType = DiskLimitType.COUNT;
+
+    /** simple stat */
+    private final AtomicInteger hitCount = new AtomicInteger(0);
+
+    /**
+     * Use this lock to synchronize reads and writes to the underlying storage mechanism.
+     */
+    protected ReentrantReadWriteLock storageLock = new ReentrantReadWriteLock();
+
+    /**
+     * Constructor for the DiskCache object.
+     * <p>
+     *
+     * @param cacheAttributes
+     */
+    public IndexedDiskCache(IndexedDiskCacheAttributes cacheAttributes)
+    {
+        this(cacheAttributes, null);
+    }
+
+    /**
+     * Constructor for the DiskCache object.
+     * <p>
+     *
+     * @param cattr
+     * @param elementSerializer
+     *            used if supplied, the super's super will not set a null
+     */
+    public IndexedDiskCache(IndexedDiskCacheAttributes cattr, IElementSerializer elementSerializer)
+    {
+        super(cattr);
+
+        setElementSerializer(elementSerializer);
+
+        this.cattr = cattr;
+        this.maxKeySize = cattr.getMaxKeySize();
+        this.isRealTimeOptimizationEnabled = cattr.getOptimizeAtRemoveCount() > 0;
+        this.isShutdownOptimizationEnabled = cattr.isOptimizeOnShutdown();
+        this.logCacheName = "Region [" + getCacheName() + "] ";
+        this.diskLimitType = cattr.getDiskLimitType();
+        // Make a clean file name
+        this.fileName = getCacheName().replaceAll("[^a-zA-Z0-9-_\\.]", "_");
+        this.keyHash = createInitialKeyMap();
+        this.queuedPutList = new ConcurrentSkipListSet<>(new PositionComparator());
+        this.recycle = new ConcurrentSkipListSet<>();
+
+        try
+        {
+            initializeFileSystem(cattr);
+            initializeKeysAndData(cattr);
+
+            // Initialization finished successfully, so set alive to true.
+            setAlive(true);
+            log.info("{0}: Indexed Disk Cache is alive.", logCacheName);
+
+            // TODO: Should we improve detection of whether or not the file should be optimized.
+            if (isRealTimeOptimizationEnabled && keyHash.size() > 0)
+            {
+                // Kick off a real time optimization, in case we didn't do a final optimization.
+                doOptimizeRealTime();
+            }
+        }
+        catch (IOException e)
+        {
+            log.error("{0}: Failure initializing for fileName: {1} and directory: {2}",
+                    logCacheName, fileName, this.rafDir.getAbsolutePath(), e);
+        }
+    }
+
+    /**
+     * Tries to create the root directory if it does not already exist.
+     * <p>
+     *
+     * @param cattr
+     */
+    private void initializeFileSystem(IndexedDiskCacheAttributes cattr)
+    {
+        this.rafDir = cattr.getDiskPath();
+        log.info("{0}: Cache file root directory: {1}", logCacheName, rafDir);
+    }
+
+    /**
+     * Creates the key and data disk caches.
+     * <p>
+     * Loads any keys if they are present and ClearDiskOnStartup is false.
+     * <p>
+     *
+     * @param cattr
+     * @throws IOException
+     */
+    private void initializeKeysAndData(IndexedDiskCacheAttributes cattr) throws IOException
+    {
+        this.dataFile = new IndexedDisk(new File(rafDir, fileName + ".data"), getElementSerializer());
+        this.keyFile = new IndexedDisk(new File(rafDir, fileName + ".key"), getElementSerializer());
+
+        if (cattr.isClearDiskOnStartup())
+        {
+            log.info("{0}: ClearDiskOnStartup is set to true.  Ingnoring any persisted data.",
+                    logCacheName);
+            initializeEmptyStore();
+        }
+        else if (keyFile.length() > 0)
+        {
+            // If the key file has contents, try to initialize the keys
+            // from it. In no keys are loaded reset the data file.
+            initializeStoreFromPersistedData();
+        }
+        else
+        {
+            // Otherwise start with a new empty map for the keys, and reset
+            // the data file if it has contents.
+            initializeEmptyStore();
+        }
+    }
+
+    /**
+     * Initializes an empty disk cache.
+     * <p>
+     *
+     * @throws IOException
+     */
+    private void initializeEmptyStore() throws IOException
+    {
+        this.keyHash.clear();
+
+        if (dataFile.length() > 0)
+        {
+            dataFile.reset();
+        }
+    }
+
+    /**
+     * Loads any persisted data and checks for consistency. If there is a consistency issue, the
+     * files are cleared.
+     * <p>
+     *
+     * @throws IOException
+     */
+    private void initializeStoreFromPersistedData() throws IOException
+    {
+        loadKeys();
+
+        if (keyHash.isEmpty())
+        {
+            dataFile.reset();
+        }
+        else
+        {
+            boolean isOk = checkKeyDataConsistency(false);
+            if (!isOk)
+            {
+                keyHash.clear();
+                keyFile.reset();
+                dataFile.reset();
+                log.warn("{0}: Corruption detected. Resetting data and keys files.", logCacheName);
+            }
+            else
+            {
+                synchronized (this)
+                {
+                    startupSize = keyHash.size();
+                }
+            }
+        }
+    }
+
+    /**
+     * Loads the keys from the .key file. The keys are stored in a HashMap on disk. This is
+     * converted into a LRUMap.
+     */
+    protected void loadKeys()
+    {
+        log.debug("{0}: Loading keys for {1}", () -> logCacheName, () -> keyFile.toString());
+
+        storageLock.writeLock().lock();
+
+        try
+        {
+            // clear a key map to use.
+            keyHash.clear();
+
+            HashMap<K, IndexedDiskElementDescriptor> keys = keyFile.readObject(
+                new IndexedDiskElementDescriptor(0, (int) keyFile.length() - IndexedDisk.HEADER_SIZE_BYTES));
+
+            if (keys != null)
+            {
+                log.debug("{0}: Found {1} in keys file.", logCacheName, keys.size());
+
+                keyHash.putAll(keys);
+
+                log.info("{0}: Loaded keys from [{1}], key count: {2}; up to {3} will be available.",
+                        () -> logCacheName, () -> fileName, () -> keyHash.size(), () -> maxKeySize);
+            }
+
+            if (log.isTraceEnabled())
+            {
+                dump(false);
+            }
+        }
+        catch (Exception e)
+        {
+            log.error("{0}: Problem loading keys for file {1}", logCacheName, fileName, e);
+        }
+        finally
+        {
+            storageLock.writeLock().unlock();
+        }
+    }
+
+    /**
+     * Check for minimal consistency between the keys and the datafile. Makes sure no starting
+     * positions in the keys exceed the file length.
+     * <p>
+     * The caller should take the appropriate action if the keys and data are not consistent.
+     *
+     * @param checkForDedOverlaps
+     *            if <code>true</code>, do a more thorough check by checking for
+     *            data overlap
+     * @return <code>true</code> if the test passes
+     */
+    private boolean checkKeyDataConsistency(boolean checkForDedOverlaps)
+    {
+        ElapsedTimer timer = new ElapsedTimer();
+        log.debug("{0}: Performing inital consistency check", logCacheName);
+
+        boolean isOk = true;
+        long fileLength = 0;
+        try
+        {
+            fileLength = dataFile.length();
+
+            for (Map.Entry<K, IndexedDiskElementDescriptor> e : keyHash.entrySet())
+            {
+                IndexedDiskElementDescriptor ded = e.getValue();
+
+                isOk = ded.pos + IndexedDisk.HEADER_SIZE_BYTES + ded.len <= fileLength;
+
+                if (!isOk)
+                {
+                    log.warn("{0}: The dataFile is corrupted!\n raf.length() = {1}\n ded.pos = {2}",
+                            logCacheName, fileLength, ded.pos);
+                    break;
+                }
+            }
+
+            if (isOk && checkForDedOverlaps)
+            {
+                isOk = checkForDedOverlaps(createPositionSortedDescriptorList());
+            }
+        }
+        catch (IOException e)
+        {
+            log.error(e);
+            isOk = false;
+        }
+
+        log.info("{0}: Finished inital consistency check, isOk = {1} in {2}",
+                logCacheName, isOk, timer.getElapsedTimeString());
+
+        return isOk;
+    }
+
+    /**
+     * Detects any overlapping elements. This expects a sorted list.
+     * <p>
+     * The total length of an item is IndexedDisk.RECORD_HEADER + ded.len.
+     * <p>
+     *
+     * @param sortedDescriptors
+     * @return false if there are overlaps.
+     */
+    protected boolean checkForDedOverlaps(IndexedDiskElementDescriptor[] sortedDescriptors)
+    {
+        ElapsedTimer timer = new ElapsedTimer();
+        boolean isOk = true;
+        long expectedNextPos = 0;
+        for (int i = 0; i < sortedDescriptors.length; i++)
+        {
+            IndexedDiskElementDescriptor ded = sortedDescriptors[i];
+            if (expectedNextPos > ded.pos)
+            {
+                log.error("{0}: Corrupt file: overlapping deds {1}", logCacheName, ded);
+                isOk = false;
+                break;
+            }
+            else
+            {
+                expectedNextPos = ded.pos + IndexedDisk.HEADER_SIZE_BYTES + ded.len;
+            }
+        }
+        log.debug("{0}: Check for DED overlaps took {1} ms.", () -> logCacheName,
+                () -> timer.getElapsedTime());
+
+        return isOk;
+    }
+
+    /**
+     * Saves key file to disk. This converts the LRUMap to a HashMap for deserialization.
+     */
+    protected void saveKeys()
+    {
+        try
+        {
+            log.info("{0}: Saving keys to: {1}, key count: {2}",
+                    () -> logCacheName, () -> fileName, () -> keyHash.size());
+
+            keyFile.reset();
+
+            HashMap<K, IndexedDiskElementDescriptor> keys = new HashMap<>();
+            keys.putAll(keyHash);
+
+            if (keys.size() > 0)
+            {
+                keyFile.writeObject(keys, 0);
+            }
+
+            log.info("{0}: Finished saving keys.", logCacheName);
+        }
+        catch (IOException e)
+        {
+            log.error("{0}: Problem storing keys.", logCacheName, e);
+        }
+    }
+
+    /**
+     * Update the disk cache. Called from the Queue. Makes sure the Item has not been retrieved from
+     * purgatory while in queue for disk. Remove items from purgatory when they go to disk.
+     * <p>
+     *
+     * @param ce
+     *            The ICacheElement&lt;K, V&gt; to put to disk.
+     */
+    @Override
+    protected void processUpdate(ICacheElement<K, V> ce)
+    {
+        if (!isAlive())
+        {
+            log.error("{0}: No longer alive; aborting put of key = {1}",
+                    () -> logCacheName, () -> ce.getKey());
+            return;
+        }
+
+        log.debug("{0}: Storing element on disk, key: {1}",
+                () -> logCacheName, () -> ce.getKey());
+
+        IndexedDiskElementDescriptor ded = null;
+
+        // old element with same key
+        IndexedDiskElementDescriptor old = null;
+
+        try
+        {
+            byte[] data = getElementSerializer().serialize(ce);
+
+            // make sure this only locks for one particular cache region
+            storageLock.writeLock().lock();
+            try
+            {
+                old = keyHash.get(ce.getKey());
+
+                // Item with the same key already exists in file.
+                // Try to reuse the location if possible.
+                if (old != null && data.length <= old.len)
+                {
+                    // Reuse the old ded. The defrag relies on ded updates by reference, not
+                    // replacement.
+                    ded = old;
+                    ded.len = data.length;
+                }
+                else
+                {
+                    // we need this to compare in the recycle bin
+                    ded = new IndexedDiskElementDescriptor(dataFile.length(), data.length);
+
+                    if (doRecycle)
+                    {
+                        IndexedDiskElementDescriptor rep = recycle.ceiling(ded);
+                        if (rep != null)
+                        {
+                            // remove element from recycle bin
+                            recycle.remove(rep);
+                            ded = rep;
+                            ded.len = data.length;
+                            recycleCnt++;
+                            this.adjustBytesFree(ded, false);
+                            log.debug("{0}: using recycled ded {1} rep.len = {2} ded.len = {3}",
+                                    logCacheName, ded.pos, rep.len, ded.len);
+                        }
+                    }
+
+                    // Put it in the map
+                    keyHash.put(ce.getKey(), ded);
+
+                    if (queueInput)
+                    {
+                        queuedPutList.add(ded);
+                        log.debug("{0}: added to queued put list. {1}",
+                                () -> logCacheName, () -> queuedPutList.size());
+                    }
+
+                    // add the old slot to the recycle bin
+                    if (old != null)
+                    {
+                        addToRecycleBin(old);
+                    }
+                }
+
+                dataFile.write(ded, data);
+            }
+            finally
+            {
+                storageLock.writeLock().unlock();
+            }
+
+            log.debug("{0}: Put to file: {1}, key: {2}, position: {3}, size: {4}",
+                    logCacheName, fileName, ce.getKey(), ded.pos, ded.len);
+        }
+        catch (IOException e)
+        {
+            log.error("{0}: Failure updating element, key: {1} old: {2}",
+                    logCacheName, ce.getKey(), old, e);
+        }
+    }
+
+    /**
+     * Gets the key, then goes to disk to get the object.
+     * <p>
+     *
+     * @param key
+     * @return ICacheElement&lt;K, V&gt; or null
+     * @see AbstractDiskCache#doGet
+     */
+    @Override
+    protected ICacheElement<K, V> processGet(K key)
+    {
+        if (!isAlive())
+        {
+            log.error("{0}: No longer alive so returning null for key = {1}",
+                    logCacheName, key);
+            return null;
+        }
+
+        log.debug("{0}: Trying to get from disk: {1}", logCacheName, key);
+
+        ICacheElement<K, V> object = null;
+        try
+        {
+            storageLock.readLock().lock();
+            try
+            {
+                object = readElement(key);
+            }
+            finally
+            {
+                storageLock.readLock().unlock();
+            }
+
+            if (object != null)
+            {
+                hitCount.incrementAndGet();
+            }
+        }
+        catch (IOException ioe)
+        {
+            log.error("{0}: Failure getting from disk, key = {1}", logCacheName, key, ioe);
+            reset();
+        }
+        return object;
+    }
+
+    /**
+     * Gets matching items from the cache.
+     * <p>
+     *
+     * @param pattern
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache matching keys
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> processGetMatching(String pattern)
+    {
+        Map<K, ICacheElement<K, V>> elements = new HashMap<>();
+        Set<K> keyArray = null;
+        storageLock.readLock().lock();
+        try
+        {
+            keyArray = new HashSet<>(keyHash.keySet());
+        }
+        finally
+        {
+            storageLock.readLock().unlock();
+        }
+
+        Set<K> matchingKeys = getKeyMatcher().getMatchingKeysFromArray(pattern, keyArray);
+
+        for (K key : matchingKeys)
+        {
+            ICacheElement<K, V> element = processGet(key);
+            if (element != null)
+            {
+                elements.put(key, element);
+            }
+        }
+        return elements;
+    }
+
+    /**
+     * Reads the item from disk.
+     * <p>
+     *
+     * @param key
+     * @return ICacheElement
+     * @throws IOException
+     */
+    private ICacheElement<K, V> readElement(K key) throws IOException
+    {
+        ICacheElement<K, V> object = null;
+
+        IndexedDiskElementDescriptor ded = keyHash.get(key);
+
+        if (ded != null)
+        {
+            log.debug("{0}: Found on disk, key: ", logCacheName, key);
+
+            try
+            {
+                ICacheElement<K, V> readObject = dataFile.readObject(ded);
+                object = readObject;
+                // TODO consider checking key equality and throwing if there is a failure
+            }
+            catch (IOException e)
+            {
+                log.error("{0}: IO Exception, Problem reading object from file", logCacheName, e);
+                throw e;
+            }
+            catch (Exception e)
+            {
+                log.error("{0}: Exception, Problem reading object from file", logCacheName, e);
+                throw new IOException(logCacheName + "Problem reading object from disk.", e);
+            }
+        }
+
+        return object;
+    }
+
+    /**
+     * Return the keys in this cache.
+     * <p>
+     *
+     * @see org.apache.commons.jcs3.auxiliary.disk.AbstractDiskCache#getKeySet()
+     */
+    @Override
+    public Set<K> getKeySet() throws IOException
+    {
+        HashSet<K> keys = new HashSet<>();
+
+        storageLock.readLock().lock();
+
+        try
+        {
+            keys.addAll(this.keyHash.keySet());
+        }
+        finally
+        {
+            storageLock.readLock().unlock();
+        }
+
+        return keys;
+    }
+
+    /**
+     * Returns true if the removal was successful; or false if there is nothing to remove. Current
+     * implementation always result in a disk orphan.
+     * <p>
+     *
+     * @return true if at least one item was removed.
+     * @param key
+     */
+    @Override
+    protected boolean processRemove(K key)
+    {
+        if (!isAlive())
+        {
+            log.error("{0}: No longer alive so returning false for key = {1}", logCacheName, key);
+            return false;
+        }
+
+        if (key == null)
+        {
+            return false;
+        }
+
+        boolean reset = false;
+        boolean removed = false;
+        try
+        {
+            storageLock.writeLock().lock();
+
+            if (key instanceof String && key.toString().endsWith(NAME_COMPONENT_DELIMITER))
+            {
+                removed = performPartialKeyRemoval((String) key);
+            }
+            else if (key instanceof GroupAttrName && ((GroupAttrName<?>) key).attrName == null)
+            {
+                removed = performGroupRemoval(((GroupAttrName<?>) key).groupId);
+            }
+            else
+            {
+                removed = performSingleKeyRemoval(key);
+            }
+        }
+        finally
+        {
+            storageLock.writeLock().unlock();
+        }
+
+        if (reset)
+        {
+            reset();
+        }
+
+        // this increments the remove count.
+        // there is no reason to call this if an item was not removed.
+        if (removed)
+        {
+            doOptimizeRealTime();
+        }
+
+        return removed;
+    }
+
+    /**
+     * Iterates over the keyset. Builds a list of matches. Removes all the keys in the list. Does
+     * not remove via the iterator, since the map impl may not support it.
+     * <p>
+     * This operates under a lock obtained in doRemove().
+     * <p>
+     *
+     * @param key
+     * @return true if there was a match
+     */
+    private boolean performPartialKeyRemoval(String key)
+    {
+        boolean removed = false;
+
+        // remove all keys of the same name hierarchy.
+        List<K> itemsToRemove = new LinkedList<>();
+
+        for (K k : keyHash.keySet())
+        {
+            if (k instanceof String && k.toString().startsWith(key))
+            {
+                itemsToRemove.add(k);
+            }
+        }
+
+        // remove matches.
+        for (K fullKey : itemsToRemove)
+        {
+            // Don't add to recycle bin here
+            // https://issues.apache.org/jira/browse/JCS-67
+            performSingleKeyRemoval(fullKey);
+            removed = true;
+            // TODO this needs to update the remove count separately
+        }
+
+        return removed;
+    }
+
+    /**
+     * Remove all elements from the group. This does not use the iterator to remove. It builds a
+     * list of group elements and then removes them one by one.
+     * <p>
+     * This operates under a lock obtained in doRemove().
+     * <p>
+     *
+     * @param key
+     * @return true if an element was removed
+     */
+    private boolean performGroupRemoval(GroupId key)
+    {
+        boolean removed = false;
+
+        // remove all keys of the same name group.
+        List<K> itemsToRemove = new LinkedList<>();
+
+        // remove all keys of the same name hierarchy.
+        for (K k : keyHash.keySet())
+        {
+            if (k instanceof GroupAttrName && ((GroupAttrName<?>) k).groupId.equals(key))
+            {
+                itemsToRemove.add(k);
+            }
+        }
+
+        // remove matches.
+        for (K fullKey : itemsToRemove)
+        {
+            // Don't add to recycle bin here
+            // https://issues.apache.org/jira/browse/JCS-67
+            performSingleKeyRemoval(fullKey);
+            removed = true;
+            // TODO this needs to update the remove count separately
+        }
+
+        return removed;
+    }
+
+    /**
+     * Removes an individual key from the cache.
+     * <p>
+     * This operates under a lock obtained in doRemove().
+     * <p>
+     *
+     * @param key
+     * @return true if an item was removed.
+     */
+    private boolean performSingleKeyRemoval(K key)
+    {
+        boolean removed;
+        // remove single item.
+        IndexedDiskElementDescriptor ded = keyHash.remove(key);
+        removed = ded != null;
+        addToRecycleBin(ded);
+
+        log.debug("{0}: Disk removal: Removed from key hash, key [{1}] removed = {2}",
+                logCacheName, key, removed);
+        return removed;
+    }
+
+    /**
+     * Remove all the items from the disk cache by reseting everything.
+     */
+    @Override
+    public void processRemoveAll()
+    {
+        ICacheEvent<String> cacheEvent =
+                createICacheEvent(getCacheName(), "all", ICacheEventLogger.REMOVEALL_EVENT);
+        try
+        {
+            reset();
+        }
+        finally
+        {
+            logICacheEvent(cacheEvent);
+        }
+    }
+
+    /**
+     * Reset effectively clears the disk cache, creating new files, recycle bins, and keymaps.
+     * <p>
+     * It can be used to handle errors by last resort, force content update, or removeall.
+     */
+    private void reset()
+    {
+        log.info("{0}: Resetting cache", logCacheName);
+
+        try
+        {
+            storageLock.writeLock().lock();
+
+            if (dataFile != null)
+            {
+                dataFile.close();
+            }
+
+            File dataFileTemp = new File(rafDir, fileName + ".data");
+            Files.delete(dataFileTemp.toPath());
+
+            if (keyFile != null)
+            {
+                keyFile.close();
+            }
+            File keyFileTemp = new File(rafDir, fileName + ".key");
+            Files.delete(keyFileTemp.toPath());
+
+            dataFile = new IndexedDisk(dataFileTemp, getElementSerializer());
+            keyFile = new IndexedDisk(keyFileTemp, getElementSerializer());
+
+            this.recycle.clear();
+            this.keyHash.clear();
+        }
+        catch (IOException e)
+        {
+            log.error("{0}: Failure resetting state", logCacheName, e);
+        }
+        finally
+        {
+            storageLock.writeLock().unlock();
+        }
+    }
+
+    /**
+     * Create the map for keys that contain the index position on disk.
+     *
+     * @return a new empty Map for keys and IndexedDiskElementDescriptors
+     */
+    private Map<K, IndexedDiskElementDescriptor> createInitialKeyMap()
+    {
+        Map<K, IndexedDiskElementDescriptor> keyMap = null;
+        if (maxKeySize >= 0)
+        {
+            if (this.diskLimitType == DiskLimitType.COUNT)
+            {
+                keyMap = new LRUMapCountLimited(maxKeySize);
+            }
+            else
+            {
+                keyMap = new LRUMapSizeLimited(maxKeySize);
+            }
+
+            log.info("{0}: Set maxKeySize to: \"{1}\"", logCacheName, maxKeySize);
+        }
+        else
+        {
+            // If no max size, use a plain map for memory and processing efficiency.
+            keyMap = new HashMap<>();
+            // keyHash = Collections.synchronizedMap( new HashMap() );
+            log.info("{0}: Set maxKeySize to unlimited", logCacheName);
+        }
+
+        return keyMap;
+    }
+
+    /**
+     * Dispose of the disk cache in a background thread. Joins against this thread to put a cap on
+     * the disposal time.
+     * <p>
+     * TODO make dispose window configurable.
+     */
+    @Override
+    public void processDispose()
+    {
+        ICacheEvent<String> cacheEvent = createICacheEvent(getCacheName(), "none", ICacheEventLogger.DISPOSE_EVENT);
+        try
+        {
+            Thread t = new Thread(this::disposeInternal, "IndexedDiskCache-DisposalThread");
+            t.start();
+            // wait up to 60 seconds for dispose and then quit if not done.
+            try
+            {
+                t.join(60 * 1000);
+            }
+            catch (InterruptedException ex)
+            {
+                log.error("{0}: Interrupted while waiting for disposal thread to finish.",
+                        logCacheName, ex);
+            }
+        }
+        finally
+        {
+            logICacheEvent(cacheEvent);
+        }
+    }
+
+    /**
+     * Internal method that handles the disposal.
+     */
+    protected void disposeInternal()
+    {
+        if (!isAlive())
+        {
+            log.error("{0}: Not alive and dispose was called, filename: {1}",
+                    logCacheName, fileName);
+            return;
+        }
+
+        // Prevents any interaction with the cache while we're shutting down.
+        setAlive(false);
+
+        Thread optimizationThread = currentOptimizationThread;
+        if (isRealTimeOptimizationEnabled && optimizationThread != null)
+        {
+            // Join with the current optimization thread.
+            log.debug("{0}: In dispose, optimization already in progress; waiting for completion.",
+                    logCacheName);
+
+            try
+            {
+                optimizationThread.join();
+            }
+            catch (InterruptedException e)
+            {
+                log.error("{0}: Unable to join current optimization thread.",
+                        logCacheName, e);
+            }
+        }
+        else if (isShutdownOptimizationEnabled && this.getBytesFree() > 0)
+        {
+            optimizeFile();
+        }
+
+        saveKeys();
+
+        try
+        {
+            log.debug("{0}: Closing files, base filename: {1}", logCacheName,
+                    fileName);
+            dataFile.close();
+            dataFile = null;
+            keyFile.close();
+            keyFile = null;
+        }
+        catch (IOException e)
+        {
+            log.error("{0}: Failure closing files in dispose, filename: {1}",
+                    logCacheName, fileName, e);
+        }
+
+        log.info("{0}: Shutdown complete.", logCacheName);
+    }
+
+    /**
+     * Add descriptor to recycle bin if it is not null. Adds the length of the item to the bytes
+     * free.
+     * <p>
+     * This is called in three places: (1) When an item is removed. All item removals funnel down to the removeSingleItem method.
+     * (2) When an item on disk is updated with a value that will not fit in the previous slot. (3) When the max key size is
+     * reached, the freed slot will be added.
+     * <p>
+     *
+     * @param ded
+     */
+    protected void addToRecycleBin(IndexedDiskElementDescriptor ded)
+    {
+        // reuse the spot
+        if (ded != null)
+        {
+            storageLock.readLock().lock();
+
+            try
+            {
+                adjustBytesFree(ded, true);
+
+                if (doRecycle)
+                {
+                    recycle.add(ded);
+                    log.debug("{0}: recycled ded {1}", logCacheName, ded);
+                }
+            }
+            finally
+            {
+                storageLock.readLock().unlock();
+            }
+        }
+    }
+
+    /**
+     * Performs the check for optimization, and if it is required, do it.
+     */
+    protected void doOptimizeRealTime()
+    {
+        if (isRealTimeOptimizationEnabled && !isOptimizing
+            && removeCount++ >= cattr.getOptimizeAtRemoveCount())
+        {
+            isOptimizing = true;
+
+            log.info("{0}: Optimizing file. removeCount [{1}] OptimizeAtRemoveCount [{2}]",
+                    logCacheName, removeCount, cattr.getOptimizeAtRemoveCount());
+
+            if (currentOptimizationThread == null)
+            {
+                storageLock.writeLock().lock();
+
+                try
+                {
+                    if (currentOptimizationThread == null)
+                    {
+                        currentOptimizationThread = new Thread(() -> {
+                            optimizeFile();
+                            currentOptimizationThread = null;
+                        }, "IndexedDiskCache-OptimizationThread");
+                    }
+                }
+                finally
+                {
+                    storageLock.writeLock().unlock();
+                }
+
+                if (currentOptimizationThread != null)
+                {
+                    currentOptimizationThread.start();
+                }
+            }
+        }
+    }
+
+    /**
+     * File optimization is handled by this method. It works as follows:
+     * <ol>
+     * <li>Shutdown recycling and turn on queuing of puts.</li>
+     * <li>Take a snapshot of the current descriptors. If there are any removes, ignore them, as they will be compacted during the
+     * next optimization.</li>
+     * <li>Optimize the snapshot. For each descriptor:
+     * <ol>
+     * <li>Obtain the write-lock.</li>
+     * <li>Shift the element on the disk, in order to compact out the free space.</li>
+     * <li>Release the write-lock. This allows elements to still be accessible during optimization.</li>
+     * </ol>
+     * </li>
+     * <li>Obtain the write-lock.</li>
+     * <li>All queued puts are made at the end of the file. Optimize these under a single write-lock.</li>
+     * <li>Truncate the file.</li>
+     * <li>Release the write-lock.</li>
+     * <li>Restore system to standard operation.</li>
+     * </ol>
+     */
+    protected void optimizeFile()
+    {
+        ElapsedTimer timer = new ElapsedTimer();
+        timesOptimized++;
+        log.info("{0}: Beginning Optimization #{1}", logCacheName, timesOptimized);
+
+        // CREATE SNAPSHOT
+        IndexedDiskElementDescriptor[] defragList = null;
+
+        storageLock.writeLock().lock();
+
+        try
+        {
+            queueInput = true;
+            // shut off recycle while we're optimizing,
+            doRecycle = false;
+            defragList = createPositionSortedDescriptorList();
+        }
+        finally
+        {
+            storageLock.writeLock().unlock();
+        }
+
+        // Defrag the file outside of the write lock. This allows a move to be made,
+        // and yet have the element still accessible for reading or writing.
+        long expectedNextPos = defragFile(defragList, 0);
+
+        // ADD THE QUEUED ITEMS to the end and then truncate
+        storageLock.writeLock().lock();
+
+        try
+        {
+            try
+            {
+                if (!queuedPutList.isEmpty())
+                {
+                    defragList = queuedPutList.toArray(new IndexedDiskElementDescriptor[queuedPutList.size()]);
+
+                    // pack them at the end
+                    expectedNextPos = defragFile(defragList, expectedNextPos);
+                }
+                // TRUNCATE THE FILE
+                dataFile.truncate(expectedNextPos);
+            }
+            catch (IOException e)
+            {
+                log.error("{0}: Error optimizing queued puts.", logCacheName, e);
+            }
+
+            // RESTORE NORMAL OPERATION
+            removeCount = 0;
+            resetBytesFree();
+            this.recycle.clear();
+            queuedPutList.clear();
+            queueInput = false;
+            // turn recycle back on.
+            doRecycle = true;
+            isOptimizing = false;
+        }
+        finally
+        {
+            storageLock.writeLock().unlock();
+        }
+
+        log.info("{0}: Finished #{1}, Optimization took {2}",
+                logCacheName, timesOptimized, timer.getElapsedTimeString());
+    }
+
+    /**
+     * Defragments the file in place by compacting out the free space (i.e., moving records
+     * forward). If there were no gaps the resulting file would be the same size as the previous
+     * file. This must be supplied an ordered defragList.
+     * <p>
+     *
+     * @param defragList
+     *            sorted list of descriptors for optimization
+     * @param startingPos
+     *            the start position in the file
+     * @return this is the potential new file end
+     */
+    private long defragFile(IndexedDiskElementDescriptor[] defragList, long startingPos)
+    {
+        ElapsedTimer timer = new ElapsedTimer();
+        long preFileSize = 0;
+        long postFileSize = 0;
+        long expectedNextPos = 0;
+        try
+        {
+            preFileSize = this.dataFile.length();
+            // find the first gap in the disk and start defragging.
+            expectedNextPos = startingPos;
+            for (int i = 0; i < defragList.length; i++)
+            {
+                storageLock.writeLock().lock();
+                try
+                {
+                    if (expectedNextPos != defragList[i].pos)
+                    {
+                        dataFile.move(defragList[i], expectedNextPos);
+                    }
+                    expectedNextPos = defragList[i].pos + IndexedDisk.HEADER_SIZE_BYTES + defragList[i].len;
+                }
+                finally
+                {
+                    storageLock.writeLock().unlock();
+                }
+            }
+
+            postFileSize = this.dataFile.length();
+
+            // this is the potential new file end
+            return expectedNextPos;
+        }
+        catch (IOException e)
+        {
+            log.error("{0}: Error occurred during defragmentation.", logCacheName, e);
+        }
+        finally
+        {
+            log.info("{0}: Defragmentation took {1}. File Size (before={2}) (after={3}) (truncating to {4})",
+                    logCacheName, timer.getElapsedTimeString(), preFileSize, postFileSize, expectedNextPos);
+        }
+
+        return 0;
+    }
+
+    /**
+     * Creates a snapshot of the IndexedDiskElementDescriptors in the keyHash and returns them
+     * sorted by position in the dataFile.
+     * <p>
+     *
+     * @return IndexedDiskElementDescriptor[]
+     */
+    private IndexedDiskElementDescriptor[] createPositionSortedDescriptorList()
+    {
+        List<IndexedDiskElementDescriptor> defragList = new ArrayList<>(keyHash.values());
+        Collections.sort(defragList, new PositionComparator());
+
+        return defragList.toArray(new IndexedDiskElementDescriptor[0]);
+    }
+
+    /**
+     * Returns the current cache size.
+     * <p>
+     *
+     * @return The size value
+     */
+    @Override
+    public int getSize()
+    {
+        return keyHash.size();
+    }
+
+    /**
+     * Returns the size of the recycle bin in number of elements.
+     * <p>
+     *
+     * @return The number of items in the bin.
+     */
+    protected int getRecyleBinSize()
+    {
+        return this.recycle.size();
+    }
+
+    /**
+     * Returns the number of times we have used spots from the recycle bin.
+     * <p>
+     *
+     * @return The number of spots used.
+     */
+    protected int getRecyleCount()
+    {
+        return this.recycleCnt;
+    }
+
+    /**
+     * Returns the number of bytes that are free. When an item is removed, its length is recorded.
+     * When a spot is used form the recycle bin, the length of the item stored is recorded.
+     * <p>
+     *
+     * @return The number bytes free on the disk file.
+     */
+    protected long getBytesFree()
+    {
+        return this.bytesFree.get();
+    }
+
+    /**
+     * Resets the number of bytes that are free.
+     */
+    private void resetBytesFree()
+    {
+        this.bytesFree.set(0);
+    }
+
+    /**
+     * To subtract you can pass in false for add..
+     * <p>
+     *
+     * @param ded
+     * @param add
+     */
+    private void adjustBytesFree(IndexedDiskElementDescriptor ded, boolean add)
+    {
+        if (ded != null)
+        {
+            int amount = ded.len + IndexedDisk.HEADER_SIZE_BYTES;
+
+            if (add)
+            {
+                this.bytesFree.addAndGet(amount);
+            }
+            else
+            {
+                this.bytesFree.addAndGet(-amount);
+            }
+        }
+    }
+
+    /**
+     * This is for debugging and testing.
+     * <p>
+     *
+     * @return the length of the data file.
+     * @throws IOException
+     */
+    protected long getDataFileSize() throws IOException
+    {
+        long size = 0;
+
+        storageLock.readLock().lock();
+
+        try
+        {
+            if (dataFile != null)
+            {
+                size = dataFile.length();
+            }
+        }
+        finally
+        {
+            storageLock.readLock().unlock();
+        }
+
+        return size;
+    }
+
+    /**
+     * For debugging. This dumps the values by default.
+     */
+    public void dump()
+    {
+        dump(true);
+    }
+
+    /**
+     * For debugging.
+     * <p>
+     *
+     * @param dumpValues
+     *            A boolean indicating if values should be dumped.
+     */
+    public void dump(boolean dumpValues)
+    {
+        if (log.isTraceEnabled())
+        {
+            log.trace("{0}: [dump] Number of keys: {1}", logCacheName, keyHash.size());
+
+            for (Map.Entry<K, IndexedDiskElementDescriptor> e : keyHash.entrySet())
+            {
+                K key = e.getKey();
+                IndexedDiskElementDescriptor ded = e.getValue();
+
+                log.trace("{0}: [dump] Disk element, key: {1}, pos: {2}, len: {3}" +
+                        (dumpValues ? ", val: " + get(key) : ""),
+                        logCacheName, key, ded.pos, ded.len);
+            }
+        }
+    }
+
+    /**
+     * @return Returns the AuxiliaryCacheAttributes.
+     */
+    @Override
+    public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
+    {
+        return this.cattr;
+    }
+
+    /**
+     * Returns info about the disk cache.
+     * <p>
+     *
+     * @see org.apache.commons.jcs3.auxiliary.AuxiliaryCache#getStatistics()
+     */
+    @Override
+    public synchronized IStats getStatistics()
+    {
+        IStats stats = new Stats();
+        stats.setTypeName("Indexed Disk Cache");
+
+        ArrayList<IStatElement<?>> elems = new ArrayList<>();
+
+        elems.add(new StatElement<>("Is Alive", Boolean.valueOf(isAlive())));
+        elems.add(new StatElement<>("Key Map Size", Integer.valueOf(this.keyHash != null ? this.keyHash.size() : -1)));
+        try
+        {
+            elems.add(
+                    new StatElement<>("Data File Length", Long.valueOf(this.dataFile != null ? this.dataFile.length() : -1L)));
+        }
+        catch (IOException e)
+        {
+            log.error(e);
+        }
+        elems.add(new StatElement<>("Max Key Size", this.maxKeySize));
+        elems.add(new StatElement<>("Hit Count", this.hitCount));
+        elems.add(new StatElement<>("Bytes Free", this.bytesFree));
+        elems.add(new StatElement<>("Optimize Operation Count", Integer.valueOf(this.removeCount)));
+        elems.add(new StatElement<>("Times Optimized", Integer.valueOf(this.timesOptimized)));
+        elems.add(new StatElement<>("Recycle Count", Integer.valueOf(this.recycleCnt)));
+        elems.add(new StatElement<>("Recycle Bin Size", Integer.valueOf(this.recycle.size())));
+        elems.add(new StatElement<>("Startup Size", Integer.valueOf(this.startupSize)));
+
+        // get the stats from the super too
+        IStats sStats = super.getStatistics();
+        elems.addAll(sStats.getStatElements());
+
+        stats.setStatElements(elems);
+
+        return stats;
+    }
+
+    /**
+     * This is exposed for testing.
+     * <p>
+     *
+     * @return Returns the timesOptimized.
+     */
+    protected int getTimesOptimized()
+    {
+        return timesOptimized;
+    }
+
+    /**
+     * This is used by the event logging.
+     * <p>
+     *
+     * @return the location of the disk, either path or ip.
+     */
+    @Override
+    protected String getDiskLocation()
+    {
+        return dataFile.getFilePath();
+    }
+
+    /**
+     * Compares IndexedDiskElementDescriptor based on their position.
+     * <p>
+     */
+    protected static final class PositionComparator implements Comparator<IndexedDiskElementDescriptor>, Serializable
+    {
+        /** serialVersionUID */
+        private static final long serialVersionUID = -8387365338590814113L;
+
+        /**
+         * Compares two descriptors based on position.
+         * <p>
+         *
+         * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
+         */
+        @Override
+        public int compare(IndexedDiskElementDescriptor ded1, IndexedDiskElementDescriptor ded2)
+        {
+            if (ded1.pos < ded2.pos)
+            {
+                return -1;
+            }
+            else if (ded1.pos == ded2.pos)
+            {
+                return 0;
+            }
+            else
+            {
+                return 1;
+            }
+        }
+    }
+
+    /**
+     * Class for recycling and lru. This implements the LRU overflow callback, so we can add items
+     * to the recycle bin. This class counts the size element to decide, when to throw away an element
+     */
+    public class LRUMapSizeLimited extends AbstractLRUMap<K, IndexedDiskElementDescriptor>
+    {
+        /**
+         * <code>tag</code> tells us which map we are working on.
+         */
+        public static final String TAG = "orig";
+
+        // size of the content in kB
+        private AtomicInteger contentSize;
+        private int maxSize;
+
+        /**
+         * Default
+         */
+        public LRUMapSizeLimited()
+        {
+            this(-1);
+        }
+
+        /**
+         * @param maxKeySize
+         */
+        public LRUMapSizeLimited(int maxKeySize)
+        {
+            super();
+            this.maxSize = maxKeySize;
+            this.contentSize = new AtomicInteger(0);
+        }
+
+        // keep the content size in kB, so 2^31 kB is reasonable value
+        private void subLengthFromCacheSize(IndexedDiskElementDescriptor value)
+        {
+            contentSize.addAndGet((value.len + IndexedDisk.HEADER_SIZE_BYTES) / -1024 - 1);
+        }
+
+        // keep the content size in kB, so 2^31 kB is reasonable value
+        private void addLengthToCacheSize(IndexedDiskElementDescriptor value)
+        {
+            contentSize.addAndGet((value.len + IndexedDisk.HEADER_SIZE_BYTES) / 1024 + 1);
+        }
+
+        @Override
+        public IndexedDiskElementDescriptor put(K key, IndexedDiskElementDescriptor value)
+        {
+            IndexedDiskElementDescriptor oldValue = null;
+
+            try
+            {
+                oldValue = super.put(key, value);
+            }
+            finally
+            {
+                // keep the content size in kB, so 2^31 kB is reasonable value
+                if (value != null)
+                {
+                    addLengthToCacheSize(value);
+                }
+                if (oldValue != null)
+                {
+                    subLengthFromCacheSize(oldValue);
+                }
+            }
+
+            return oldValue;
+        }
+
+        @Override
+        public IndexedDiskElementDescriptor remove(Object key)
+        {
+            IndexedDiskElementDescriptor value = null;
+
+            try
+            {
+                value = super.remove(key);
+                return value;
+            }
+            finally
+            {
+                if (value != null)
+                {
+                    subLengthFromCacheSize(value);
+                }
+            }
+        }
+
+        /**
+         * This is called when the may key size is reached. The least recently used item will be
+         * passed here. We will store the position and size of the spot on disk in the recycle bin.
+         * <p>
+         *
+         * @param key
+         * @param value
+         */
+        @Override
+        protected void processRemovedLRU(K key, IndexedDiskElementDescriptor value)
+        {
+            if (value != null)
+            {
+                subLengthFromCacheSize(value);
+            }
+
+            addToRecycleBin(value);
+
+            log.debug("{0}: Removing key: [{1}] from key store.", logCacheName, key);
+            log.debug("{0}: Key store size: [{1}].", logCacheName, this.size());
+
+            doOptimizeRealTime();
+        }
+
+        @Override
+        protected boolean shouldRemove()
+        {
+            return maxSize > 0 && contentSize.get() > maxSize && this.size() > 0;
+        }
+    }
+
+    /**
+     * Class for recycling and lru. This implements the LRU overflow callback, so we can add items
+     * to the recycle bin. This class counts the elements to decide, when to throw away an element
+     */
+
+    public class LRUMapCountLimited extends LRUMap<K, IndexedDiskElementDescriptor>
+    // implements Serializable
+    {
+        public LRUMapCountLimited(int maxKeySize)
+        {
+            super(maxKeySize);
+        }
+
+        /**
+         * This is called when the may key size is reached. The least recently used item will be
+         * passed here. We will store the position and size of the spot on disk in the recycle bin.
+         * <p>
+         *
+         * @param key
+         * @param value
+         */
+        @Override
+        protected void processRemovedLRU(K key, IndexedDiskElementDescriptor value)
+        {
+            addToRecycleBin(value);
+            log.debug("{0}: Removing key: [{1}] from key store.", logCacheName, key);
+            log.debug("{0}: Key store size: [{1}].", logCacheName, this.size());
+
+            doOptimizeRealTime();
+        }
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheAttributes.java
new file mode 100644
index 0000000..f90a72b
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheAttributes.java
@@ -0,0 +1,154 @@
+package org.apache.commons.jcs3.auxiliary.disk.indexed;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.auxiliary.disk.AbstractDiskCacheAttributes;
+
+/**
+ * Configuration class for the Indexed Disk Cache
+ */
+public class IndexedDiskCacheAttributes
+    extends AbstractDiskCacheAttributes
+{
+    /** Don't change. */
+    private static final long serialVersionUID = -2190863599358782950L;
+
+    /** default value */
+    private static final int DEFAULT_maxKeySize = 5000;
+
+    /** -1 means no limit. */
+    private int maxKeySize = DEFAULT_maxKeySize;
+
+    /** default to -1, i.e., don't optimize until shutdown */
+    private int optimizeAtRemoveCount = -1;
+
+    /** Should we optimize on shutdown. */
+    public static final boolean DEFAULT_OPTIMIZE_ON_SHUTDOWN = true;
+
+    /** Should we optimize on shutdown. */
+    private boolean optimizeOnShutdown = DEFAULT_OPTIMIZE_ON_SHUTDOWN;
+
+    /** Should we clear the disk on startup. */
+    public static final boolean DEFAULT_CLEAR_DISK_ON_STARTUP = false;
+
+    /** Should we clear the disk on startup. If true the contents of disk are cleared. */
+    private boolean clearDiskOnStartup = DEFAULT_CLEAR_DISK_ON_STARTUP;
+
+    /**
+     * Constructor for the DiskCacheAttributes object
+     */
+    public IndexedDiskCacheAttributes()
+    {
+        super();
+    }
+
+    /**
+     * Gets the maxKeySize attribute of the DiskCacheAttributes object
+     * <p>
+     * @return The maxKeySize value
+     */
+    public int getMaxKeySize()
+    {
+        return this.maxKeySize;
+    }
+
+    /**
+     * Sets the maxKeySize attribute of the DiskCacheAttributes object
+     * <p>
+     * @param maxKeySize The new maxKeySize value
+     */
+    public void setMaxKeySize( int maxKeySize )
+    {
+        this.maxKeySize = maxKeySize;
+    }
+
+    /**
+     * Gets the optimizeAtRemoveCount attribute of the DiskCacheAttributes object
+     * <p>
+     * @return The optimizeAtRemoveCount value
+     */
+    public int getOptimizeAtRemoveCount()
+    {
+        return this.optimizeAtRemoveCount;
+    }
+
+    /**
+     * Sets the optimizeAtRemoveCount attribute of the DiskCacheAttributes object This number
+     * determines how often the disk cache should run real time optimizations.
+     * <p>
+     * @param cnt The new optimizeAtRemoveCount value
+     */
+    public void setOptimizeAtRemoveCount( int cnt )
+    {
+        this.optimizeAtRemoveCount = cnt;
+    }
+
+    /**
+     * @param optimizeOnShutdown The optimizeOnShutdown to set.
+     */
+    public void setOptimizeOnShutdown( boolean optimizeOnShutdown )
+    {
+        this.optimizeOnShutdown = optimizeOnShutdown;
+    }
+
+    /**
+     * @return Returns the optimizeOnShutdown.
+     */
+    public boolean isOptimizeOnShutdown()
+    {
+        return optimizeOnShutdown;
+    }
+
+    /**
+     * @param clearDiskOnStartup the clearDiskOnStartup to set
+     */
+    public void setClearDiskOnStartup( boolean clearDiskOnStartup )
+    {
+        this.clearDiskOnStartup = clearDiskOnStartup;
+    }
+
+    /**
+     * @return the clearDiskOnStartup
+     */
+    public boolean isClearDiskOnStartup()
+    {
+        return clearDiskOnStartup;
+    }
+
+    /**
+     * Write out the values for debugging purposes.
+     * <p>
+     * @return String
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder str = new StringBuilder();
+        str.append( "IndexedDiskCacheAttributes " );
+        str.append( "\n diskPath = " + super.getDiskPath() );
+        str.append( "\n maxPurgatorySize   = " + super.getMaxPurgatorySize() );
+        str.append( "\n maxKeySize  = " + maxKeySize );
+        str.append( "\n optimizeAtRemoveCount  = " + optimizeAtRemoveCount );
+        str.append( "\n shutdownSpoolTimeLimit  = " + super.getShutdownSpoolTimeLimit() );
+        str.append( "\n optimizeOnShutdown  = " + optimizeOnShutdown );
+        str.append( "\n clearDiskOnStartup  = " + clearDiskOnStartup );
+        return str.toString();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheFactory.java
new file mode 100644
index 0000000..cd5c660
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheFactory.java
@@ -0,0 +1,62 @@
+package org.apache.commons.jcs3.auxiliary.disk.indexed;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCacheFactory;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * Creates disk cache instances.
+ */
+public class IndexedDiskCacheFactory
+    extends AbstractAuxiliaryCacheFactory
+{
+    /** The logger. */
+    private static final Log log = LogManager.getLog( IndexedDiskCacheFactory.class );
+
+    /**
+     * Create an instance of an IndexedDiskCache.
+     * <p>
+     * @param iaca cache attributes of this cache instance
+     * @param cacheMgr This allows auxiliaries to reference the manager without assuming that it is
+     *            a singleton. This will allow JCS to be a non-singleton. Also, it makes it easier to
+     *            test.
+     * @param cacheEventLogger
+     * @param elementSerializer
+     * @return IndexedDiskCache
+     */
+    @Override
+    public <K, V> IndexedDiskCache<K, V> createCache( AuxiliaryCacheAttributes iaca, ICompositeCacheManager cacheMgr,
+                                       ICacheEventLogger cacheEventLogger, IElementSerializer elementSerializer )
+    {
+        IndexedDiskCacheAttributes idca = (IndexedDiskCacheAttributes) iaca;
+        log.debug( "Creating DiskCache for attributes = {0}", idca );
+
+        IndexedDiskCache<K, V> cache = new IndexedDiskCache<>( idca, elementSerializer );
+        cache.setCacheEventLogger( cacheEventLogger );
+
+        return cache;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskDumper.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskDumper.java
new file mode 100644
index 0000000..ffb25d9
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskDumper.java
@@ -0,0 +1,57 @@
+package org.apache.commons.jcs3.auxiliary.disk.indexed;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.Serializable;
+
+
+/**
+ * Used to dump out a Disk cache from disk for debugging. This is meant to be
+ * run as a command line utility for
+ */
+public class IndexedDiskDumper
+{
+    /**
+     * The main program for the DiskDumper class
+     * <p>
+     * Creates a disk cache and then calls dump, which write out the contents to
+     * a debug log.
+     * <p>
+     * @param args
+     *            The command line arguments
+     */
+    public static void main( String[] args )
+    {
+        if ( args.length != 1 )
+        {
+            System.out.println( "Usage: java org.apache.commons.jcs3.auxiliary.disk.DiskDump <cache_name>" );
+            System.exit( 0 );
+        }
+
+        IndexedDiskCacheAttributes attr = new IndexedDiskCacheAttributes();
+
+        attr.setCacheName( args[0] );
+        attr.setDiskPath( args[0] );
+
+        IndexedDiskCache<Serializable, Serializable> dc = new IndexedDiskCache<>( attr );
+        dc.dump( true );
+        System.exit( 0 );
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskElementDescriptor.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskElementDescriptor.java
new file mode 100644
index 0000000..2158c46
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskElementDescriptor.java
@@ -0,0 +1,132 @@
+package org.apache.commons.jcs3.auxiliary.disk.indexed;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.Serializable;
+
+/**
+ * Disk objects are located by descriptor entries. These are saved on shutdown and loaded into
+ * memory on startup.
+ */
+public class IndexedDiskElementDescriptor
+    implements Serializable, Comparable<IndexedDiskElementDescriptor>
+{
+    /** Don't change */
+    private static final long serialVersionUID = -3029163572847659450L;
+
+    /** Position of the cache data entry on disk. */
+    long pos;
+
+    /** Number of bytes the serialized form of the cache data takes. */
+    int len;
+
+    /**
+     * Constructs a usable disk element descriptor.
+     * <p>
+     * @param pos
+     * @param len
+     */
+    public IndexedDiskElementDescriptor( long pos, int len )
+    {
+        this.pos = pos;
+        this.len = len;
+    }
+
+    /**
+     * @return debug string
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        buf.append( "[DED: " );
+        buf.append( " pos = " + pos );
+        buf.append( " len = " + len );
+        buf.append( "]" );
+        return buf.toString();
+    }
+
+    /**
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode()
+    {
+        return Long.valueOf(this.pos).hashCode() ^ Integer.valueOf(len).hashCode();
+    }
+
+    /**
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object o)
+    {
+    	if (o == null)
+    	{
+    		return false;
+    	}
+    	else if (o instanceof IndexedDiskElementDescriptor)
+        {
+    		IndexedDiskElementDescriptor ided = (IndexedDiskElementDescriptor)o;
+            return pos == ided.pos && len == ided.len;
+        }
+
+        return false;
+    }
+
+    /**
+     * Compares based on length, then on pos descending.
+     * <p>
+     * @param o Object
+     * @return int
+     */
+    @Override
+    public int compareTo( IndexedDiskElementDescriptor o )
+    {
+        if ( o == null )
+        {
+            return 1;
+        }
+
+        if ( o.len == len )
+        {
+        	if ( o.pos == pos )
+        	{
+        		return 0;
+        	}
+        	else if ( o.pos < pos )
+        	{
+        		return -1;
+        	}
+        	else
+        	{
+        		return 1;
+        	}
+        }
+        else if ( o.len > len )
+        {
+            return -1;
+        }
+        else
+        {
+            return 1;
+        }
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/JDBCDiskCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/JDBCDiskCache.java
new file mode 100644
index 0000000..720fdf2
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/JDBCDiskCache.java
@@ -0,0 +1,878 @@
+package org.apache.commons.jcs3.auxiliary.disk.jdbc;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.sql.DataSource;
+
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.disk.AbstractDiskCache;
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.dsfactory.DataSourceFactory;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEvent;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+import org.apache.commons.jcs3.engine.stats.StatElement;
+import org.apache.commons.jcs3.engine.stats.behavior.IStatElement;
+import org.apache.commons.jcs3.engine.stats.behavior.IStats;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.serialization.StandardSerializer;
+
+/**
+ * This is the jdbc disk cache plugin.
+ * <p>
+ * It expects a table created by the following script. The table name is configurable.
+ * <p>
+ *
+ * <pre>
+ *                       drop TABLE JCS_STORE;
+ *                       CREATE TABLE JCS_STORE
+ *                       (
+ *                       CACHE_KEY                  VARCHAR(250)          NOT NULL,
+ *                       REGION                     VARCHAR(250)          NOT NULL,
+ *                       ELEMENT                    BLOB,
+ *                       CREATE_TIME                TIMESTAMP,
+ *                       UPDATE_TIME_SECONDS        BIGINT,
+ *                       MAX_LIFE_SECONDS           BIGINT,
+ *                       SYSTEM_EXPIRE_TIME_SECONDS BIGINT,
+ *                       IS_ETERNAL                 CHAR(1),
+ *                       PRIMARY KEY (CACHE_KEY, REGION)
+ *                       );
+ * </pre>
+ * <p>
+ * The cleanup thread will delete non eternal items where (now - create time) &gt; max life seconds *
+ * 1000
+ * <p>
+ * To speed up the deletion the SYSTEM_EXPIRE_TIME_SECONDS is used instead. It is recommended that
+ * an index be created on this column is you will have over a million records.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class JDBCDiskCache<K, V>
+    extends AbstractDiskCache<K, V>
+{
+    /** The local logger. */
+    private static final Log log = LogManager.getLog( JDBCDiskCache.class );
+
+    /** custom serialization */
+    private IElementSerializer elementSerializer = new StandardSerializer();
+
+    /** configuration */
+    private JDBCDiskCacheAttributes jdbcDiskCacheAttributes;
+
+    /** # of times update was called */
+    private final AtomicInteger updateCount = new AtomicInteger(0);
+
+    /** # of times get was called */
+    private final AtomicInteger getCount = new AtomicInteger(0);
+
+    /** # of times getMatching was called */
+    private final AtomicInteger getMatchingCount = new AtomicInteger(0);
+
+    /** if count % interval == 0 then log */
+    private static final int LOG_INTERVAL = 100;
+
+    /** db connection pool */
+    private DataSourceFactory dsFactory = null;
+
+    /** tracks optimization */
+    private TableState tableState;
+
+    /**
+     * Constructs a JDBC Disk Cache for the provided cache attributes. The table state object is
+     * used to mark deletions.
+     * <p>
+     * @param cattr the configuration object for this cache
+     * @param dsFactory the DataSourceFactory for this cache
+     * @param tableState an object to track table operations
+     * @param compositeCacheManager the global cache manager
+     */
+    public JDBCDiskCache( JDBCDiskCacheAttributes cattr, DataSourceFactory dsFactory, TableState tableState,
+                          ICompositeCacheManager compositeCacheManager )
+    {
+        super( cattr );
+
+        setTableState( tableState );
+        setJdbcDiskCacheAttributes( cattr );
+
+        log.info( "jdbcDiskCacheAttributes = {0}", () -> getJdbcDiskCacheAttributes() );
+
+        // This initializes the pool access.
+        this.dsFactory = dsFactory;
+
+        // Initialization finished successfully, so set alive to true.
+        setAlive(true);
+    }
+
+    /**
+     * Inserts or updates. By default it will try to insert. If the item exists we will get an
+     * error. It will then update. This behavior is configurable. The cache can be configured to
+     * check before inserting.
+     * <p>
+     * @param ce
+     */
+    @Override
+    protected void processUpdate( ICacheElement<K, V> ce )
+    {
+    	updateCount.incrementAndGet();
+
+        log.debug( "updating, ce = {0}", ce );
+
+        try (Connection con = getDataSource().getConnection())
+        {
+            log.debug( "Putting [{0}] on disk.",  () -> ce.getKey());
+
+            byte[] element;
+
+            try
+            {
+                element = getElementSerializer().serialize( ce );
+            }
+            catch ( IOException e )
+            {
+                log.error( "Could not serialize element", e );
+                return;
+            }
+
+            insertOrUpdate( ce, con, element );
+        }
+        catch ( SQLException e )
+        {
+            log.error( "Problem getting connection.", e );
+        }
+
+        if ( log.isInfoEnabled() )
+        {
+            if ( updateCount.get() % LOG_INTERVAL == 0 )
+            {
+                // TODO make a log stats method
+                log.info( "Update Count [{0}]", updateCount);
+            }
+        }
+    }
+
+    /**
+     * If test before insert it true, we check to see if the element exists. If the element exists
+     * we will update. Otherwise, we try inserting.  If this fails because the item exists, we will
+     * update.
+     * <p>
+     * @param ce
+     * @param con
+     * @param element
+     */
+    private void insertOrUpdate( ICacheElement<K, V> ce, Connection con, byte[] element )
+    {
+        boolean exists = false;
+
+        // First do a query to determine if the element already exists
+        if ( this.getJdbcDiskCacheAttributes().isTestBeforeInsert() )
+        {
+            exists = doesElementExist( ce, con );
+        }
+
+        // If it doesn't exist, insert it, otherwise update
+        if ( !exists )
+        {
+            exists = insertRow( ce, con, element );
+        }
+
+        // update if it exists.
+        if ( exists )
+        {
+            updateRow( ce, con, element );
+        }
+    }
+
+    /**
+     * This inserts a new row in the database.
+     * <p>
+     * @param ce
+     * @param con
+     * @param element
+     * @return true if the insertion fails because the record exists.
+     */
+    private boolean insertRow( ICacheElement<K, V> ce, Connection con, byte[] element )
+    {
+        boolean exists = false;
+        String sqlI = "insert into "
+                + getJdbcDiskCacheAttributes().getTableName()
+                + " (CACHE_KEY, REGION, ELEMENT, MAX_LIFE_SECONDS, IS_ETERNAL, CREATE_TIME, UPDATE_TIME_SECONDS, SYSTEM_EXPIRE_TIME_SECONDS) "
+                + " values (?, ?, ?, ?, ?, ?, ?, ?)";
+
+        try (PreparedStatement psInsert = con.prepareStatement( sqlI ))
+        {
+            psInsert.setString( 1, (String) ce.getKey() );
+            psInsert.setString( 2, this.getCacheName() );
+            psInsert.setBytes( 3, element );
+            psInsert.setLong( 4, ce.getElementAttributes().getMaxLife() );
+            if ( ce.getElementAttributes().getIsEternal() )
+            {
+                psInsert.setString( 5, "T" );
+            }
+            else
+            {
+                psInsert.setString( 5, "F" );
+            }
+            Timestamp createTime = new Timestamp( ce.getElementAttributes().getCreateTime() );
+            psInsert.setTimestamp( 6, createTime );
+
+            long now = System.currentTimeMillis() / 1000;
+            psInsert.setLong( 7, now );
+
+            long expireTime = now + ce.getElementAttributes().getMaxLife();
+            psInsert.setLong( 8, expireTime );
+
+            psInsert.execute();
+        }
+        catch ( SQLException e )
+        {
+            if ("23000".equals(e.getSQLState()))
+            {
+                exists = true;
+            }
+            else
+            {
+                log.error( "Could not insert element", e );
+            }
+
+            // see if it exists, if we didn't already
+            if ( !exists && !this.getJdbcDiskCacheAttributes().isTestBeforeInsert() )
+            {
+                exists = doesElementExist( ce, con );
+            }
+        }
+
+        return exists;
+    }
+
+    /**
+     * This updates a row in the database.
+     * <p>
+     * @param ce
+     * @param con
+     * @param element
+     */
+    private void updateRow( ICacheElement<K, V> ce, Connection con, byte[] element )
+    {
+        String sqlU = "update " + getJdbcDiskCacheAttributes().getTableName()
+                + " set ELEMENT  = ?, CREATE_TIME = ?, UPDATE_TIME_SECONDS = ?, " + " SYSTEM_EXPIRE_TIME_SECONDS = ? "
+                + " where CACHE_KEY = ? and REGION = ?";
+
+        try (PreparedStatement psUpdate = con.prepareStatement( sqlU ))
+        {
+            psUpdate.setBytes( 1, element );
+
+            Timestamp createTime = new Timestamp( ce.getElementAttributes().getCreateTime() );
+            psUpdate.setTimestamp( 2, createTime );
+
+            long now = System.currentTimeMillis() / 1000;
+            psUpdate.setLong( 3, now );
+
+            long expireTime = now + ce.getElementAttributes().getMaxLife();
+            psUpdate.setLong( 4, expireTime );
+
+            psUpdate.setString( 5, (String) ce.getKey() );
+            psUpdate.setString( 6, this.getCacheName() );
+            psUpdate.execute();
+
+            log.debug( "ran update {0}", sqlU );
+        }
+        catch ( SQLException e )
+        {
+            log.error( "Error executing update sql [{0}]", sqlU, e );
+        }
+    }
+
+    /**
+     * Does an element exist for this key?
+     * <p>
+     * @param ce the cache element
+     * @param con a database connection
+     * @return boolean
+     */
+    protected boolean doesElementExist( ICacheElement<K, V> ce, Connection con )
+    {
+        boolean exists = false;
+        // don't select the element, since we want this to be fast.
+        String sqlS = "select CACHE_KEY from " + getJdbcDiskCacheAttributes().getTableName()
+            + " where REGION = ? and CACHE_KEY = ?";
+
+        try (PreparedStatement psSelect = con.prepareStatement( sqlS ))
+        {
+            psSelect.setString( 1, this.getCacheName() );
+            psSelect.setString( 2, (String) ce.getKey() );
+
+            try (ResultSet rs = psSelect.executeQuery())
+            {
+                exists = rs.next();
+            }
+
+            log.debug( "[{0}] existing status is {1}", ce.getKey(), exists );
+        }
+        catch ( SQLException e )
+        {
+            log.error( "Problem looking for item before insert.", e );
+        }
+
+        return exists;
+    }
+
+    /**
+     * Queries the database for the value. If it gets a result, the value is deserialized.
+     * <p>
+     * @param key
+     * @return ICacheElement
+     * @see org.apache.commons.jcs3.auxiliary.disk.AbstractDiskCache#get(Object)
+     */
+    @Override
+    protected ICacheElement<K, V> processGet( K key )
+    {
+    	getCount.incrementAndGet();
+
+        log.debug( "Getting [{0}] from disk", key );
+
+        if ( !isAlive() )
+        {
+            return null;
+        }
+
+        ICacheElement<K, V> obj = null;
+
+        byte[] data = null;
+        try
+        {
+            // region, key
+            String selectString = "select ELEMENT from " + getJdbcDiskCacheAttributes().getTableName()
+                + " where REGION = ? and CACHE_KEY = ?";
+
+            try (Connection con = getDataSource().getConnection())
+            {
+                try (PreparedStatement psSelect = con.prepareStatement( selectString ))
+                {
+                    psSelect.setString( 1, this.getCacheName() );
+                    psSelect.setString( 2, key.toString() );
+
+                    try (ResultSet rs = psSelect.executeQuery())
+                    {
+                        if ( rs.next() )
+                        {
+                            data = rs.getBytes( 1 );
+                        }
+                        if ( data != null )
+                        {
+                            try
+                            {
+                                // USE THE SERIALIZER
+                                obj = getElementSerializer().deSerialize( data, null );
+                            }
+                            catch ( Exception e )
+                            {
+                                log.error( "Problem getting item for key [{0}]", key, e );
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        catch ( SQLException sqle )
+        {
+            log.error( "Caught a SQL exception trying to get the item for key [{0}]",
+                    key, sqle );
+        }
+
+        if ( log.isInfoEnabled() )
+        {
+            if ( getCount.get() % LOG_INTERVAL == 0 )
+            {
+                // TODO make a log stats method
+                log.info( "Get Count [{0}]", getCount );
+            }
+        }
+        return obj;
+    }
+
+    /**
+     * This will run a like query. It will try to construct a usable query but different
+     * implementations will be needed to adjust the syntax.
+     * <p>
+     * @param pattern
+     * @return key,value map
+     */
+    @Override
+    protected Map<K, ICacheElement<K, V>> processGetMatching( String pattern )
+    {
+    	getMatchingCount.incrementAndGet();
+
+        log.debug( "Getting [{0}] from disk", pattern);
+
+        if ( !isAlive() )
+        {
+            return null;
+        }
+
+        Map<K, ICacheElement<K, V>> results = new HashMap<>();
+
+        try
+        {
+            // region, key
+            String selectString = "select CACHE_KEY, ELEMENT from " + getJdbcDiskCacheAttributes().getTableName()
+                + " where REGION = ? and CACHE_KEY like ?";
+
+            try (Connection con = getDataSource().getConnection())
+            {
+                try (PreparedStatement psSelect = con.prepareStatement( selectString ))
+                {
+                    psSelect.setString( 1, this.getCacheName() );
+                    psSelect.setString( 2, constructLikeParameterFromPattern( pattern ) );
+
+                    try (ResultSet rs = psSelect.executeQuery())
+                    {
+                        while ( rs.next() )
+                        {
+                            String key = rs.getString( 1 );
+                            byte[] data = rs.getBytes( 2 );
+                            if ( data != null )
+                            {
+                                try
+                                {
+                                    // USE THE SERIALIZER
+                                    ICacheElement<K, V> value = getElementSerializer().deSerialize( data, null );
+                                    results.put( (K) key, value );
+                                }
+                                catch ( Exception e )
+                                {
+                                    log.error( "Problem getting items for pattern [{0}]", pattern, e );
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        catch ( SQLException sqle )
+        {
+            log.error( "Caught a SQL exception trying to get items for pattern [{0}]",
+                    pattern, sqle );
+        }
+
+        if ( log.isInfoEnabled() )
+        {
+            if ( getMatchingCount.get() % LOG_INTERVAL == 0 )
+            {
+                // TODO make a log stats method
+                log.info( "Get Matching Count [{0}]", getMatchingCount);
+            }
+        }
+        return results;
+    }
+
+    /**
+     * @param pattern
+     * @return String to use in the like query.
+     */
+    public String constructLikeParameterFromPattern( String pattern )
+    {
+        String likePattern = pattern.replaceAll( "\\.\\+", "%" );
+        likePattern = likePattern.replaceAll( "\\.", "_" );
+
+        log.debug( "pattern = [{0}]", likePattern );
+
+        return likePattern;
+    }
+
+    /**
+     * Returns true if the removal was successful; or false if there is nothing to remove. Current
+     * implementation always results in a disk orphan.
+     * <p>
+     * @param key
+     * @return boolean
+     */
+    @Override
+    protected boolean processRemove( K key )
+    {
+        // remove single item.
+        String sql = "delete from " + getJdbcDiskCacheAttributes().getTableName()
+            + " where REGION = ? and CACHE_KEY = ?";
+
+        try (Connection con = getDataSource().getConnection())
+        {
+            boolean partial = false;
+            if ( key instanceof String && key.toString().endsWith( NAME_COMPONENT_DELIMITER ) )
+            {
+                // remove all keys of the same name group.
+                sql = "delete from " + getJdbcDiskCacheAttributes().getTableName()
+                    + " where REGION = ? and CACHE_KEY like ?";
+                partial = true;
+            }
+
+            try (PreparedStatement psSelect = con.prepareStatement( sql ))
+            {
+                psSelect.setString( 1, this.getCacheName() );
+                if ( partial )
+                {
+                    psSelect.setString( 2, key.toString() + "%" );
+                }
+                else
+                {
+                    psSelect.setString( 2, key.toString() );
+                }
+
+                psSelect.executeUpdate();
+
+                setAlive(true);
+            }
+            catch ( SQLException e )
+            {
+                log.error( "Problem creating statement. sql [{0}]", sql, e );
+                setAlive(false);
+            }
+        }
+        catch ( SQLException e )
+        {
+            log.error( "Problem updating cache.", e );
+            reset();
+        }
+        return false;
+    }
+
+    /**
+     * This should remove all elements. The auxiliary can be configured to forbid this behavior. If
+     * remove all is not allowed, the method balks.
+     */
+    @Override
+    protected void processRemoveAll()
+    {
+        // it should never get here from the abstract disk cache.
+        if ( this.jdbcDiskCacheAttributes.isAllowRemoveAll() )
+        {
+            try (Connection con = getDataSource().getConnection())
+            {
+                String sql = "delete from " + getJdbcDiskCacheAttributes().getTableName() + " where REGION = ?";
+
+                try (PreparedStatement psDelete = con.prepareStatement( sql ))
+                {
+                    psDelete.setString( 1, this.getCacheName() );
+                    setAlive(true);
+                    psDelete.executeUpdate();
+                }
+                catch ( SQLException e )
+                {
+                    log.error( "Problem creating statement.", e );
+                    setAlive(false);
+                }
+            }
+            catch ( SQLException e )
+            {
+                log.error( "Problem removing all.", e );
+                reset();
+            }
+        }
+        else
+        {
+            log.info( "RemoveAll was requested but the request was not fulfilled: "
+                    + "allowRemoveAll is set to false." );
+        }
+    }
+
+    /**
+     * Removed the expired. (now - create time) &gt; max life seconds * 1000
+     * <p>
+     * @return the number deleted
+     */
+    protected int deleteExpired()
+    {
+        int deleted = 0;
+
+        try (Connection con = getDataSource().getConnection())
+        {
+            // The shrinker thread might kick in before the table is created
+            // So check if the table exists first
+            DatabaseMetaData dmd = con.getMetaData();
+            ResultSet result = dmd.getTables(null, null,
+                    getJdbcDiskCacheAttributes().getTableName(), null);
+
+            if (result.next())
+            {
+                getTableState().setState( TableState.DELETE_RUNNING );
+                long now = System.currentTimeMillis() / 1000;
+
+                String sql = "delete from " + getJdbcDiskCacheAttributes().getTableName()
+                    + " where IS_ETERNAL = ? and REGION = ? and ? > SYSTEM_EXPIRE_TIME_SECONDS";
+
+                try (PreparedStatement psDelete = con.prepareStatement( sql ))
+                {
+                    psDelete.setString( 1, "F" );
+                    psDelete.setString( 2, this.getCacheName() );
+                    psDelete.setLong( 3, now );
+
+                    setAlive(true);
+
+                    deleted = psDelete.executeUpdate();
+                }
+                catch ( SQLException e )
+                {
+                    log.error( "Problem creating statement.", e );
+                    setAlive(false);
+                }
+
+                logApplicationEvent( getAuxiliaryCacheAttributes().getName(), "deleteExpired",
+                                     "Deleted expired elements.  URL: " + getDiskLocation() );
+            }
+            else
+            {
+                log.warn( "Trying to shrink non-existing table [{0}]",
+                        getJdbcDiskCacheAttributes().getTableName() );
+            }
+        }
+        catch ( SQLException e )
+        {
+            logError( getAuxiliaryCacheAttributes().getName(), "deleteExpired",
+                    e.getMessage() + " URL: " + getDiskLocation() );
+            log.error( "Problem removing expired elements from the table.", e );
+            reset();
+        }
+        finally
+        {
+            getTableState().setState( TableState.FREE );
+        }
+
+        return deleted;
+    }
+
+    /**
+     * Typically this is used to handle errors by last resort, force content update, or removeall
+     */
+    public void reset()
+    {
+        // nothing
+    }
+
+    /** Shuts down the pool */
+    @Override
+    public void processDispose()
+    {
+        ICacheEvent<K> cacheEvent = createICacheEvent( getCacheName(), (K)"none", ICacheEventLogger.DISPOSE_EVENT );
+
+        try
+        {
+        	dsFactory.close();
+        }
+        catch ( SQLException e )
+        {
+            log.error( "Problem shutting down.", e );
+        }
+        finally
+        {
+            logICacheEvent( cacheEvent );
+        }
+    }
+
+    /**
+     * Returns the current cache size. Just does a count(*) for the region.
+     * <p>
+     * @return The size value
+     */
+    @Override
+    public int getSize()
+    {
+        int size = 0;
+
+        // region, key
+        String selectString = "select count(*) from " + getJdbcDiskCacheAttributes().getTableName()
+            + " where REGION = ?";
+
+        try (Connection con = getDataSource().getConnection())
+        {
+            try (PreparedStatement psSelect = con.prepareStatement( selectString ))
+            {
+                psSelect.setString( 1, this.getCacheName() );
+
+                try (ResultSet rs = psSelect.executeQuery())
+                {
+                    if ( rs.next() )
+                    {
+                        size = rs.getInt( 1 );
+                    }
+                }
+            }
+        }
+        catch ( SQLException e )
+        {
+            log.error( "Problem getting size.", e );
+        }
+
+        return size;
+    }
+
+    /**
+     * Return the keys in this cache.
+     * <p>
+     * @see org.apache.commons.jcs3.auxiliary.disk.AbstractDiskCache#getKeySet()
+     */
+    @Override
+    public Set<K> getKeySet() throws IOException
+    {
+        throw new UnsupportedOperationException( "Groups not implemented." );
+        // return null;
+    }
+
+    /**
+     * @param elementSerializer The elementSerializer to set.
+     */
+    @Override
+    public void setElementSerializer( IElementSerializer elementSerializer )
+    {
+        this.elementSerializer = elementSerializer;
+    }
+
+    /**
+     * @return Returns the elementSerializer.
+     */
+    @Override
+    public IElementSerializer getElementSerializer()
+    {
+        return elementSerializer;
+    }
+
+    /**
+     * @param jdbcDiskCacheAttributes The jdbcDiskCacheAttributes to set.
+     */
+    protected void setJdbcDiskCacheAttributes( JDBCDiskCacheAttributes jdbcDiskCacheAttributes )
+    {
+        this.jdbcDiskCacheAttributes = jdbcDiskCacheAttributes;
+    }
+
+    /**
+     * @return Returns the jdbcDiskCacheAttributes.
+     */
+    protected JDBCDiskCacheAttributes getJdbcDiskCacheAttributes()
+    {
+        return jdbcDiskCacheAttributes;
+    }
+
+    /**
+     * @return Returns the AuxiliaryCacheAttributes.
+     */
+    @Override
+    public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
+    {
+        return this.getJdbcDiskCacheAttributes();
+    }
+
+    /**
+     * Extends the parent stats.
+     * <p>
+     * @return IStats
+     */
+    @Override
+    public IStats getStatistics()
+    {
+        IStats stats = super.getStatistics();
+        stats.setTypeName( "JDBC/Abstract Disk Cache" );
+
+        List<IStatElement<?>> elems = stats.getStatElements();
+
+        elems.add(new StatElement<>( "Update Count", updateCount ) );
+        elems.add(new StatElement<>( "Get Count", getCount ) );
+        elems.add(new StatElement<>( "Get Matching Count", getMatchingCount ) );
+        elems.add(new StatElement<>( "DB URL", getJdbcDiskCacheAttributes().getUrl()) );
+
+        stats.setStatElements( elems );
+
+        return stats;
+    }
+
+    /**
+     * Returns the name of the table.
+     * <p>
+     * @return the table name or UNDEFINED
+     */
+    protected String getTableName()
+    {
+        String name = "UNDEFINED";
+        if ( this.getJdbcDiskCacheAttributes() != null )
+        {
+            name = this.getJdbcDiskCacheAttributes().getTableName();
+        }
+        return name;
+    }
+
+    /**
+     * @param tableState The tableState to set.
+     */
+    public void setTableState( TableState tableState )
+    {
+        this.tableState = tableState;
+    }
+
+    /**
+     * @return Returns the tableState.
+     */
+    public TableState getTableState()
+    {
+        return tableState;
+    }
+
+    /**
+     * This is used by the event logging.
+     * <p>
+     * @return the location of the disk, either path or ip.
+     */
+    @Override
+    protected String getDiskLocation()
+    {
+        return this.jdbcDiskCacheAttributes.getUrl();
+    }
+
+    /**
+     * Public so managers can access it.
+     * @return the dsFactory
+     * @throws SQLException if getting a data source fails
+     */
+    public DataSource getDataSource() throws SQLException
+    {
+        return dsFactory.getDataSource();
+    }
+
+    /**
+     * For debugging.
+     * <p>
+     * @return this.getStats();
+     */
+    @Override
+    public String toString()
+    {
+        return this.getStats();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/JDBCDiskCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/JDBCDiskCacheAttributes.java
new file mode 100644
index 0000000..ffd50d6
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/JDBCDiskCacheAttributes.java
@@ -0,0 +1,331 @@
+package org.apache.commons.jcs3.auxiliary.disk.jdbc;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.auxiliary.disk.AbstractDiskCacheAttributes;
+
+/**
+ * The configurator will set these values based on what is in the cache.ccf file.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class JDBCDiskCacheAttributes
+    extends AbstractDiskCacheAttributes
+{
+    /** Don't change */
+    private static final long serialVersionUID = -6535808344813320062L;
+
+    /** default */
+    private static final String DEFAULT_TABLE_NAME = "JCS_STORE";
+
+    /** DB username */
+    private String userName;
+
+    /** DB password */
+    private String password;
+
+    /** URL for the db */
+    private String url;
+
+    /** The name of the database. */
+    private String database = "";
+
+    /** The driver */
+    private String driverClassName;
+
+    /** The JNDI path. */
+    private String jndiPath;
+
+    /** The time between two JNDI lookups */
+    private long jndiTTL = 0L;
+
+    /** The table name */
+    private String tableName = DEFAULT_TABLE_NAME;
+
+    /** If false we will insert and if it fails we will update. */
+    private boolean testBeforeInsert = true;
+
+    /** This is the default limit on the maximum number of active connections. */
+    public static final int DEFAULT_MAX_TOTAL = 10;
+
+    /** Max connections allowed */
+    private int maxTotal = DEFAULT_MAX_TOTAL;
+
+    /** This is the default setting for the cleanup routine. */
+    public static final int DEFAULT_SHRINKER_INTERVAL_SECONDS = 300;
+
+    /** How often should we remove expired. */
+    private int shrinkerIntervalSeconds = DEFAULT_SHRINKER_INTERVAL_SECONDS;
+
+    /** Should we remove expired in the background. */
+    private boolean useDiskShrinker = true;
+
+    /** The default Pool Name to which the connection pool will be keyed. */
+    public static final String DEFAULT_POOL_NAME = "jcs";
+
+    /**
+     * If a pool name is supplied, the manager will attempt to load it. It should be configured in a
+     * separate section as follows. Assuming the name is "MyPool":
+     *
+     * <pre>
+     * jcs.jdbcconnectionpool.MyPool.attributes.userName=MyUserName
+     * jcs.jdbcconnectionpool.MyPool.attributes.password=MyPassword
+     * jcs.jdbcconnectionpool.MyPool.attributes.url=MyUrl
+     * jcs.jdbcconnectionpool.MyPool.attributes.maxActive=MyMaxActive
+     * jcs.jdbcconnectionpool.MyPool.attributes.driverClassName=MyDriverClassName
+     * </pre>
+     */
+    private String connectionPoolName;
+
+    /**
+     * @param userName The userName to set.
+     */
+    public void setUserName( String userName )
+    {
+        this.userName = userName;
+    }
+
+    /**
+     * @return Returns the userName.
+     */
+    public String getUserName()
+    {
+        return userName;
+    }
+
+    /**
+     * @param password The password to set.
+     */
+    public void setPassword( String password )
+    {
+        this.password = password;
+    }
+
+    /**
+     * @return Returns the password.
+     */
+    public String getPassword()
+    {
+        return password;
+    }
+
+    /**
+     * @param url The url to set.
+     */
+    public void setUrl( String url )
+    {
+        this.url = url;
+    }
+
+    /**
+     * @return Returns the url.
+     */
+    public String getUrl()
+    {
+        return url;
+    }
+
+    /**
+     * This is appended to the url.
+     * @param database The database to set.
+     */
+    public void setDatabase( String database )
+    {
+        this.database = database;
+    }
+
+    /**
+     * @return Returns the database.
+     */
+    public String getDatabase()
+    {
+        return database;
+    }
+
+    /**
+     * @param driverClassName The driverClassName to set.
+     */
+    public void setDriverClassName( String driverClassName )
+    {
+        this.driverClassName = driverClassName;
+    }
+
+    /**
+     * @return Returns the driverClassName.
+     */
+    public String getDriverClassName()
+    {
+        return driverClassName;
+    }
+
+    /**
+	 * @return the jndiPath
+	 */
+	public String getJndiPath()
+	{
+		return jndiPath;
+	}
+
+	/**
+	 * @param jndiPath the jndiPath to set
+	 */
+	public void setJndiPath(String jndiPath)
+	{
+		this.jndiPath = jndiPath;
+	}
+
+	/**
+	 * @return the jndiTTL
+	 */
+	public long getJndiTTL()
+	{
+		return jndiTTL;
+	}
+
+	/**
+	 * @param jndiTTL the jndiTTL to set
+	 */
+	public void setJndiTTL(long jndiTTL)
+	{
+		this.jndiTTL = jndiTTL;
+	}
+
+	/**
+     * @param tableName The tableName to set.
+     */
+    public void setTableName( String tableName )
+    {
+        this.tableName = tableName;
+    }
+
+    /**
+     * @return Returns the tableName.
+     */
+    public String getTableName()
+    {
+        return tableName;
+    }
+
+    /**
+     * If this is true then the disk cache will check to see if the item already exists in the
+     * database. If it is false, it will try to insert. If the insert fails it will try to update.
+     * <p>
+     * @param testBeforeInsert The testBeforeInsert to set.
+     */
+    public void setTestBeforeInsert( boolean testBeforeInsert )
+    {
+        this.testBeforeInsert = testBeforeInsert;
+    }
+
+    /**
+     * @return Returns the testBeforeInsert.
+     */
+    public boolean isTestBeforeInsert()
+    {
+        return testBeforeInsert;
+    }
+
+    /**
+     * @param maxTotal The maxTotal to set.
+     */
+    public void setMaxTotal( int maxActive )
+    {
+        this.maxTotal = maxActive;
+    }
+
+    /**
+     * @return Returns the maxTotal.
+     */
+    public int getMaxTotal()
+    {
+        return maxTotal;
+    }
+
+    /**
+     * @param shrinkerIntervalSecondsArg The shrinkerIntervalSeconds to set.
+     */
+    public void setShrinkerIntervalSeconds( int shrinkerIntervalSecondsArg )
+    {
+        this.shrinkerIntervalSeconds = shrinkerIntervalSecondsArg;
+    }
+
+    /**
+     * @return Returns the shrinkerIntervalSeconds.
+     */
+    public int getShrinkerIntervalSeconds()
+    {
+        return shrinkerIntervalSeconds;
+    }
+
+    /**
+     * @param useDiskShrinker The useDiskShrinker to set.
+     */
+    public void setUseDiskShrinker( boolean useDiskShrinker )
+    {
+        this.useDiskShrinker = useDiskShrinker;
+    }
+
+    /**
+     * @return Returns the useDiskShrinker.
+     */
+    public boolean isUseDiskShrinker()
+    {
+        return useDiskShrinker;
+    }
+
+    /**
+     * @param connectionPoolName the connectionPoolName to set
+     */
+    public void setConnectionPoolName( String connectionPoolName )
+    {
+        this.connectionPoolName = connectionPoolName;
+    }
+
+    /**
+     * @return the connectionPoolName
+     */
+    public String getConnectionPoolName()
+    {
+        return connectionPoolName;
+    }
+
+    /**
+     * For debugging.
+     * <p>
+     * @return debug string with most of the properties.
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        buf.append( "\nJDBCCacheAttributes" );
+        buf.append( "\n UserName [" + getUserName() + "]" );
+        buf.append( "\n Url [" + getUrl() + "]" );
+        buf.append( "\n Database [" + getDatabase() + "]" );
+        buf.append( "\n DriverClassName [" + getDriverClassName() + "]" );
+        buf.append( "\n TableName [" + getTableName() + "]" );
+        buf.append( "\n TestBeforeInsert [" + isTestBeforeInsert() + "]" );
+        buf.append( "\n MaxActive [" + getMaxTotal() + "]" );
+        buf.append( "\n AllowRemoveAll [" + isAllowRemoveAll() + "]" );
+        buf.append( "\n ShrinkerIntervalSeconds [" + getShrinkerIntervalSeconds() + "]" );
+        buf.append( "\n useDiskShrinker [" + isUseDiskShrinker() + "]" );
+        return buf.toString();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/JDBCDiskCacheFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/JDBCDiskCacheFactory.java
new file mode 100644
index 0000000..0e99d24
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/JDBCDiskCacheFactory.java
@@ -0,0 +1,266 @@
+package org.apache.commons.jcs3.auxiliary.disk.jdbc;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.sql.SQLException;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCacheFactory;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.dsfactory.DataSourceFactory;
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.dsfactory.JndiDataSourceFactory;
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.dsfactory.SharedPoolDataSourceFactory;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.engine.behavior.IRequireScheduler;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.config.PropertySetter;
+
+/**
+ * This factory should create JDBC auxiliary caches.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class JDBCDiskCacheFactory
+    extends AbstractAuxiliaryCacheFactory
+    implements IRequireScheduler
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog( JDBCDiskCacheFactory.class );
+
+    /**
+     * A map of TableState objects to table names. Each cache has a table state object, which is
+     * used to determine if any long processes such as deletes or optimizations are running.
+     */
+    private ConcurrentMap<String, TableState> tableStates;
+
+    /** The background scheduler, one for all regions. Injected by the configurator */
+    protected ScheduledExecutorService scheduler;
+
+    /**
+     * A map of table name to shrinker threads. This allows each table to have a different setting.
+     * It assumes that there is only one jdbc disk cache auxiliary defined per table.
+     */
+    private ConcurrentMap<String, ShrinkerThread> shrinkerThreadMap;
+
+    /** Pool name to DataSourceFactories */
+    private ConcurrentMap<String, DataSourceFactory> dsFactories;
+
+    /** props prefix */
+    protected static final String POOL_CONFIGURATION_PREFIX = "jcs.jdbcconnectionpool.";
+
+    /** .attributes */
+    protected static final String ATTRIBUTE_PREFIX = ".attributes";
+
+    /**
+     * This factory method should create an instance of the jdbc cache.
+     * <p>
+     * @param rawAttr specific cache configuration attributes
+     * @param compositeCacheManager the global cache manager
+     * @param cacheEventLogger a specific logger for cache events
+     * @param elementSerializer a serializer for cache elements
+     * @return JDBCDiskCache the cache instance
+     * @throws SQLException if the cache instance could not be created
+     */
+    @Override
+    public <K, V> JDBCDiskCache<K, V> createCache( AuxiliaryCacheAttributes rawAttr,
+            ICompositeCacheManager compositeCacheManager,
+            ICacheEventLogger cacheEventLogger, IElementSerializer elementSerializer )
+            throws SQLException
+    {
+        JDBCDiskCacheAttributes cattr = (JDBCDiskCacheAttributes) rawAttr;
+        TableState tableState = getTableState( cattr.getTableName() );
+        DataSourceFactory dsFactory = getDataSourceFactory(cattr, compositeCacheManager.getConfigurationProperties());
+
+        JDBCDiskCache<K, V> cache = new JDBCDiskCache<>( cattr, dsFactory, tableState, compositeCacheManager );
+        cache.setCacheEventLogger( cacheEventLogger );
+        cache.setElementSerializer( elementSerializer );
+
+        // create a shrinker if we need it.
+        createShrinkerWhenNeeded( cattr, cache );
+
+        return cache;
+    }
+
+    /**
+     * Initialize this factory
+     */
+    @Override
+    public void initialize()
+    {
+        super.initialize();
+        this.tableStates = new ConcurrentHashMap<>();
+        this.shrinkerThreadMap = new ConcurrentHashMap<>();
+        this.dsFactories = new ConcurrentHashMap<>();
+    }
+
+    /**
+     * Dispose of this factory, clean up shared resources
+     */
+    @Override
+    public void dispose()
+    {
+        this.tableStates.clear();
+
+        for (DataSourceFactory dsFactory : this.dsFactories.values())
+        {
+        	try
+        	{
+				dsFactory.close();
+			}
+        	catch (SQLException e)
+        	{
+        		log.error("Could not close data source factory {0}", dsFactory.getName(), e);
+			}
+        }
+
+        this.dsFactories.clear();
+        this.shrinkerThreadMap.clear();
+        super.dispose();
+    }
+
+    /**
+     * Get a table state for a given table name
+     *
+     * @param tableName
+     * @return a cached instance of the table state
+     */
+    protected TableState getTableState(String tableName)
+    {
+        return tableStates.computeIfAbsent(tableName, key -> new TableState(key));
+    }
+
+    /**
+	 * @see org.apache.commons.jcs3.engine.behavior.IRequireScheduler#setScheduledExecutorService(java.util.concurrent.ScheduledExecutorService)
+	 */
+	@Override
+	public void setScheduledExecutorService(ScheduledExecutorService scheduledExecutor)
+	{
+		this.scheduler = scheduledExecutor;
+	}
+
+	/**
+     * Get the scheduler service
+     *
+     * @return the scheduler
+     */
+    protected ScheduledExecutorService getScheduledExecutorService()
+    {
+        return scheduler;
+    }
+
+    /**
+     * If UseDiskShrinker is true then we will create a shrinker daemon if necessary.
+     * <p>
+     * @param cattr
+     * @param raf
+     */
+    protected void createShrinkerWhenNeeded( JDBCDiskCacheAttributes cattr, JDBCDiskCache<?, ?> raf )
+    {
+        // add cache to shrinker.
+        if ( cattr.isUseDiskShrinker() )
+        {
+            ScheduledExecutorService shrinkerService = getScheduledExecutorService();
+            ShrinkerThread shrinkerThread = shrinkerThreadMap.computeIfAbsent(cattr.getTableName(), key -> {
+                ShrinkerThread newShrinkerThread = new ShrinkerThread();
+
+                long intervalMillis = Math.max( 999, cattr.getShrinkerIntervalSeconds() * 1000 );
+                log.info( "Setting the shrinker to run every [{0}] ms. for table [{1}]",
+                        intervalMillis, key );
+                shrinkerService.scheduleAtFixedRate(newShrinkerThread, 0, intervalMillis, TimeUnit.MILLISECONDS);
+
+                return newShrinkerThread;
+            });
+
+            shrinkerThread.addDiskCacheToShrinkList( raf );
+        }
+    }
+
+    /**
+     * manages the DataSourceFactories.
+     * <p>
+     * @param cattr the cache configuration
+     * @param configProps the configuration properties object
+     * @return a DataSourceFactory
+     * @throws SQLException if a database access error occurs
+     */
+    protected DataSourceFactory getDataSourceFactory( JDBCDiskCacheAttributes cattr,
+                                                      Properties configProps ) throws SQLException
+    {
+    	String poolName = null;
+
+    	if (cattr.getConnectionPoolName() == null)
+    	{
+    		poolName = cattr.getCacheName() + "." + JDBCDiskCacheAttributes.DEFAULT_POOL_NAME;
+        }
+        else
+        {
+            poolName = cattr.getConnectionPoolName();
+        }
+
+
+    	DataSourceFactory dsFactory = this.dsFactories.computeIfAbsent(poolName, key -> {
+    	    DataSourceFactory newDsFactory;
+            JDBCDiskCacheAttributes dsConfig = null;
+
+            if (cattr.getConnectionPoolName() == null)
+            {
+                dsConfig = cattr;
+            }
+            else
+            {
+                dsConfig = new JDBCDiskCacheAttributes();
+                String dsConfigAttributePrefix = POOL_CONFIGURATION_PREFIX + key + ATTRIBUTE_PREFIX;
+                PropertySetter.setProperties( dsConfig,
+                        configProps,
+                        dsConfigAttributePrefix + "." );
+
+                dsConfig.setConnectionPoolName(key);
+            }
+
+            if ( dsConfig.getJndiPath() != null )
+            {
+                newDsFactory = new JndiDataSourceFactory();
+            }
+            else
+            {
+                newDsFactory = new SharedPoolDataSourceFactory();
+            }
+
+            try
+            {
+                newDsFactory.initialize(dsConfig);
+            }
+            catch (SQLException e)
+            {
+                throw new RuntimeException(e);
+            }
+    	    return newDsFactory;
+    	});
+
+    	return dsFactory;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/ShrinkerThread.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/ShrinkerThread.java
new file mode 100644
index 0000000..fa9855a
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/ShrinkerThread.java
@@ -0,0 +1,149 @@
+package org.apache.commons.jcs3.auxiliary.disk.jdbc;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Iterator;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.timing.ElapsedTimer;
+
+/**
+ * Calls delete expired on the disk caches. The shrinker is run by a clock daemon. The shrinker
+ * calls delete on each region. It pauses between calls.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class ShrinkerThread
+    implements Runnable
+{
+    /** The logger. */
+    private static final Log log = LogManager.getLog( ShrinkerThread.class );
+
+    /** A set of JDBCDiskCache objects to call deleteExpired on. */
+    private final CopyOnWriteArraySet<JDBCDiskCache<?, ?>> shrinkSet =
+            new CopyOnWriteArraySet<>();
+
+    /** Default time period to use. */
+    private static final long DEFAULT_PAUSE_BETWEEN_REGION_CALLS_MILLIS = 5000;
+
+    /**
+     * How long should we wait between calls to deleteExpired when we are iterating through the list
+     * of regions. Delete can lock the table. We want to give clients a chance to get some work
+     * done.
+     */
+    private long pauseBetweenRegionCallsMillis = DEFAULT_PAUSE_BETWEEN_REGION_CALLS_MILLIS;
+
+    /**
+     * Does nothing special.
+     */
+    protected ShrinkerThread()
+    {
+        super();
+    }
+
+    /**
+     * Adds a JDBC disk cache to the set of disk cache to shrink.
+     * <p>
+     * @param diskCache
+     */
+    public void addDiskCacheToShrinkList( JDBCDiskCache<?, ?> diskCache )
+    {
+        // the set will prevent dupes.
+        // we could also just add these to a hashmap by region name
+        // but that might cause a problem if you wanted to use two different
+        // jbdc disk caches for the same region.
+        shrinkSet.add( diskCache );
+    }
+
+    /**
+     * Calls deleteExpired on each item in the set. It pauses between each call.
+     */
+    @Override
+    public void run()
+    {
+        try
+        {
+            deleteExpiredFromAllRegisteredRegions();
+        }
+        catch ( Throwable e )
+        {
+            log.error( "Caught an exception while trying to delete expired items.", e );
+        }
+    }
+
+    /**
+     * Deletes the expired items from all the registered regions.
+     */
+    private void deleteExpiredFromAllRegisteredRegions()
+    {
+        log.info( "Running JDBC disk cache shrinker. Number of regions [{0}]",
+                () -> shrinkSet.size() );
+
+        for (Iterator<JDBCDiskCache<?, ?>> i = shrinkSet.iterator(); i.hasNext();)
+        {
+            JDBCDiskCache<?, ?> cache = i.next();
+            ElapsedTimer timer = new ElapsedTimer();
+            int deleted = cache.deleteExpired();
+
+            log.info( "Deleted [{0}] expired for region [{1}] for table [{2}] in {3} ms.",
+                    deleted, cache.getCacheName(), cache.getTableName(), timer.getElapsedTime() );
+
+            // don't pause after the last call to delete expired.
+            if ( i.hasNext() )
+            {
+                log.info( "Pausing for [{0}] ms before shrinking the next region.",
+                        this.getPauseBetweenRegionCallsMillis() );
+
+                try
+                {
+                    Thread.sleep( this.getPauseBetweenRegionCallsMillis() );
+                }
+                catch ( InterruptedException e )
+                {
+                    log.warn( "Interrupted while waiting to delete expired for the next region." );
+                }
+            }
+        }
+    }
+
+    /**
+     * How long should we wait between calls to deleteExpired when we are iterating through the list
+     * of regions.
+     * <p>
+     * @param pauseBetweenRegionCallsMillis The pauseBetweenRegionCallsMillis to set.
+     */
+    public void setPauseBetweenRegionCallsMillis( long pauseBetweenRegionCallsMillis )
+    {
+        this.pauseBetweenRegionCallsMillis = pauseBetweenRegionCallsMillis;
+    }
+
+    /**
+     * How long should we wait between calls to deleteExpired when we are iterating through the list
+     * of regions.
+     * <p>
+     * @return Returns the pauseBetweenRegionCallsMillis.
+     */
+    public long getPauseBetweenRegionCallsMillis()
+    {
+        return pauseBetweenRegionCallsMillis;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/TableState.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/TableState.java
new file mode 100644
index 0000000..11dbad0
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/TableState.java
@@ -0,0 +1,114 @@
+package org.apache.commons.jcs3.auxiliary.disk.jdbc;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.Serializable;
+
+/**
+ * This is used by various elements of the JDBC disk cache to indicate the
+ * status of a table. The MySQL disk cache, for instance, marks the status as
+ * optimizing when a scheduled optimization is taking place. This allows the
+ * cache to balk rather than block during long running optimizations.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class TableState
+    implements Serializable
+{
+    /** Don't change. */
+    private static final long serialVersionUID = -6625081552084964885L;
+
+    /** Name of the table whose state this reflects. */
+    private String tableName;
+
+    /**
+     * The table is free. It can be accessed and no potentially table locking
+     * jobs are running.
+     */
+    public static final int FREE = 0;
+
+    /** A potentially table locking deletion is running */
+    public static final int DELETE_RUNNING = 1;
+
+    /** A table locking optimization is running. */
+    public static final int OPTIMIZATION_RUNNING = 2;
+
+    /** we might want to add error */
+    private int state = FREE;
+
+    /**
+     * Construct a usable table state.
+     * <p>
+     * @param tableName
+     */
+    public TableState( String tableName )
+    {
+        this.setTableName( tableName );
+    }
+
+    /**
+     * @param tableName
+     *            The tableName to set.
+     */
+    public void setTableName( String tableName )
+    {
+        this.tableName = tableName;
+    }
+
+    /**
+     * @return Returns the tableName.
+     */
+    public String getTableName()
+    {
+        return tableName;
+    }
+
+    /**
+     * @param state
+     *            The state to set.
+     */
+    public void setState( int state )
+    {
+        this.state = state;
+    }
+
+    /**
+     * @return Returns the state.
+     */
+    public int getState()
+    {
+        return state;
+    }
+
+    /**
+     * Write out the values for debugging purposes.
+     * <p>
+     * @return String
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder str = new StringBuilder();
+        str.append( "TableState " );
+        str.append( "\n TableName = " + getTableName() );
+        str.append( "\n State = " + getState() );
+        return str.toString();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/dsfactory/DataSourceFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/dsfactory/DataSourceFactory.java
new file mode 100644
index 0000000..fbb73ac
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/dsfactory/DataSourceFactory.java
@@ -0,0 +1,85 @@
+package org.apache.commons.jcs3.auxiliary.disk.jdbc.dsfactory;

+

+/*

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements.  See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership.  The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License.  You may obtain a copy of the License at

+ *

+ *   http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing,

+ * software distributed under the License is distributed on an

+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

+ * KIND, either express or implied.  See the License for the

+ * specific language governing permissions and limitations

+ * under the License.

+ */

+

+import java.sql.SQLException;

+

+import javax.sql.DataSource;

+

+import org.apache.commons.jcs3.auxiliary.disk.jdbc.JDBCDiskCacheAttributes;

+

+

+/**

+ * A factory that returns a DataSource.

+ * Borrowed from Apache DB Torque

+ *

+ * @author <a href="mailto:jmcnally@apache.org">John McNally</a>

+ * @author <a href="mailto:fischer@seitenbau.de">Thomas Fischer</a>

+ * @version $Id: DataSourceFactory.java 1336091 2012-05-09 11:09:40Z tfischer $

+ */

+public interface DataSourceFactory

+{

+    /**

+     * Key for the configuration which contains DataSourceFactories

+     */

+    String DSFACTORY_KEY = "dsfactory";

+

+    /**

+     *  Key for the configuration which contains the fully qualified name

+     *  of the factory implementation class

+     */

+    String FACTORY_KEY = "factory";

+

+    /**

+     * @return the name of the factory.

+     */

+    String getName();

+

+    /**

+     * @return the <code>DataSource</code> configured by the factory.

+     * @throws SQLException if the source can't be returned

+     */

+    DataSource getDataSource() throws SQLException;

+

+    /**

+     * Initialize the factory.

+     *

+     * @param config the factory settings

+     * @throws SQLException Any exceptions caught during processing will be

+     *         rethrown wrapped into a SQLException.

+     */

+    void initialize(JDBCDiskCacheAttributes config)

+        throws SQLException;

+

+    /**

+     * A hook which is called when the resources of the associated DataSource

+     * can be released.

+     * After close() is called, the other methods may not work any more

+     * (e.g. getDataSource() might return null).

+     * It is not guaranteed that this method does anything. For example,

+     * we do not want to close connections retrieved via JNDI, so the

+     * JndiDataSouurceFactory does not close these connections

+     *

+     * @throws SQLException Any exceptions caught during processing will be

+     *         rethrown wrapped into a SQLException.

+     */

+    void close()

+        throws SQLException;

+}

diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/dsfactory/JndiDataSourceFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/dsfactory/JndiDataSourceFactory.java
new file mode 100644
index 0000000..7d33e01
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/dsfactory/JndiDataSourceFactory.java
@@ -0,0 +1,173 @@
+package org.apache.commons.jcs3.auxiliary.disk.jdbc.dsfactory;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.sql.SQLException;
+import java.util.Hashtable;
+import java.util.Map;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.sql.DataSource;
+
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.JDBCDiskCacheAttributes;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * A factory that looks up the DataSource from JNDI.  It is also able
+ * to deploy the DataSource based on properties found in the
+ * configuration.
+ *
+ * This factory tries to avoid excessive context lookups to improve speed.
+ * The time between two lookups can be configured. The default is 0 (no cache).
+ *
+ * Borrowed and adapted from Apache DB Torque
+ *
+ * @author <a href="mailto:jmcnally@apache.org">John McNally</a>
+ * @author <a href="mailto:thomas@vandahl.org">Thomas Vandahl</a>
+ */
+public class JndiDataSourceFactory implements DataSourceFactory
+{
+    /** The log. */
+    private static Log log = LogManager.getLog(JndiDataSourceFactory.class);
+
+    /** The name of the factory. */
+    private String name;
+
+    /** The path to get the resource from. */
+    private String path;
+
+    /** The context to get the resource from. */
+    private Context ctx;
+
+    /** A locally cached copy of the DataSource */
+    private DataSource ds = null;
+
+    /** Time of last actual lookup action */
+    private long lastLookup = 0;
+
+    /** Time between two lookups */
+    private long ttl = 0; // ms
+
+    /**
+     * @return the name of the factory.
+     */
+    @Override
+	public String getName()
+    {
+    	return name;
+    }
+
+    /**
+     * @see org.apache.commons.jcs3.auxiliary.disk.jdbc.dsfactory.DataSourceFactory#getDataSource()
+     */
+    @Override
+	public DataSource getDataSource() throws SQLException
+    {
+        long time = System.currentTimeMillis();
+
+        if (ds == null || time - lastLookup > ttl)
+        {
+            try
+            {
+                synchronized (ctx)
+                {
+                    ds = ((DataSource) ctx.lookup(path));
+                }
+                lastLookup = time;
+            }
+            catch (NamingException e)
+            {
+                throw new SQLException(e);
+            }
+        }
+
+        return ds;
+    }
+
+    /**
+     * @see org.apache.commons.jcs3.auxiliary.disk.jdbc.dsfactory.DataSourceFactory#initialize(JDBCDiskCacheAttributes)
+     */
+    @Override
+	public void initialize(JDBCDiskCacheAttributes config) throws SQLException
+    {
+    	this.name = config.getConnectionPoolName();
+        initJNDI(config);
+    }
+
+    /**
+     * Initializes JNDI.
+     *
+     * @param config where to read the settings from
+     * @throws SQLException if a property set fails
+     */
+    private void initJNDI(JDBCDiskCacheAttributes config) throws SQLException
+    {
+        log.debug("Starting initJNDI");
+
+        try
+        {
+            this.path = config.getJndiPath();
+            log.debug("JNDI path: {0}", path);
+
+            this.ttl = config.getJndiTTL();
+            log.debug("Time between context lookups: {0}", ttl);
+
+    		Hashtable<String, Object> env = new Hashtable<>();
+            ctx = new InitialContext(env);
+
+            if (log.isTraceEnabled())
+            {
+            	log.trace("Created new InitialContext");
+            	debugCtx(ctx);
+            }
+        }
+        catch (NamingException e)
+        {
+            throw new SQLException(e);
+        }
+    }
+
+    /**
+     * Does nothing. We do not want to close a dataSource retrieved from Jndi,
+     * because other applications might use it as well.
+     */
+    @Override
+	public void close()
+    {
+        // do nothing
+    }
+
+    /**
+     *
+     * @param ctx the context
+     * @throws NamingException
+     */
+    private void debugCtx(Context ctx) throws NamingException
+    {
+        log.trace("InitialContext -------------------------------");
+        Map<?, ?> env = ctx.getEnvironment();
+        log.trace("Environment properties: {0}", env.size());
+        env.forEach((key, value) -> log.trace("    {0}: {1}", key, value));
+        log.trace("----------------------------------------------");
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/dsfactory/SharedPoolDataSourceFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/dsfactory/SharedPoolDataSourceFactory.java
new file mode 100644
index 0000000..371ce16
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/dsfactory/SharedPoolDataSourceFactory.java
@@ -0,0 +1,152 @@
+package org.apache.commons.jcs3.auxiliary.disk.jdbc.dsfactory;

+

+/*

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements.  See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership.  The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License.  You may obtain a copy of the License at

+ *

+ *   http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing,

+ * software distributed under the License is distributed on an

+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

+ * KIND, either express or implied.  See the License for the

+ * specific language governing permissions and limitations

+ * under the License.

+ */

+

+import java.sql.SQLException;

+

+import javax.sql.ConnectionPoolDataSource;

+import javax.sql.DataSource;

+

+import org.apache.commons.dbcp2.cpdsadapter.DriverAdapterCPDS;

+import org.apache.commons.dbcp2.datasources.InstanceKeyDataSource;

+import org.apache.commons.dbcp2.datasources.SharedPoolDataSource;

+import org.apache.commons.jcs3.auxiliary.disk.jdbc.JDBCDiskCacheAttributes;

+import org.apache.commons.jcs3.log.Log;

+import org.apache.commons.jcs3.log.LogManager;

+

+/**

+ * A factory that looks up the DataSource using the JDBC2 pool methods.

+ *

+ * Borrowed and adapted from Apache DB Torque

+ *

+ * @author <a href="mailto:jmcnally@apache.org">John McNally</a>

+ * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>

+ */

+public class SharedPoolDataSourceFactory implements DataSourceFactory

+{

+    /** The log. */

+    private static Log log = LogManager.getLog(SharedPoolDataSourceFactory.class);

+

+    /** The name of the factory. */

+    private String name;

+

+    /** The wrapped <code>DataSource</code>. */

+    private SharedPoolDataSource ds = null;

+

+    /**

+     * @return the name of the factory.

+     */

+    @Override

+	public String getName()

+    {

+    	return name;

+    }

+

+    /**

+     * @see org.apache.commons.jcs3.auxiliary.disk.jdbc.dsfactory.DataSourceFactory#getDataSource()

+     */

+    @Override

+    public DataSource getDataSource()

+    {

+        return ds;

+    }

+

+    /**

+     * @see org.apache.commons.jcs3.auxiliary.disk.jdbc.dsfactory.DataSourceFactory#initialize(JDBCDiskCacheAttributes)

+     */

+    @Override

+	public void initialize(JDBCDiskCacheAttributes config) throws SQLException

+    {

+    	this.name = config.getConnectionPoolName();

+        ConnectionPoolDataSource cpds = initCPDS(config);

+        SharedPoolDataSource dataSource = new SharedPoolDataSource();

+        initJdbc2Pool(dataSource, config);

+        dataSource.setConnectionPoolDataSource(cpds);

+        dataSource.setMaxTotal(config.getMaxTotal());

+        this.ds = dataSource;

+    }

+

+    /**

+     * Closes the pool associated with this factory and releases it.

+     * @throws SQLException if the pool cannot be closed properly

+     */

+    @Override

+	public void close() throws SQLException

+    {

+        try

+        {

+            if (ds != null)

+            {

+                ds.close();

+            }

+        }

+        catch (Exception e)

+        {

+        	throw new SQLException("Exception caught closing data source", e);

+        }

+        ds = null;

+    }

+

+    /**

+     * Initializes the ConnectionPoolDataSource.

+     *

+     * @param config where to read the settings from

+     * @throws SQLException if a property set fails

+     * @return a configured <code>ConnectionPoolDataSource</code>

+     */

+    private ConnectionPoolDataSource initCPDS(final JDBCDiskCacheAttributes config)

+        throws SQLException

+    {

+        log.debug("Starting initCPDS");

+

+        DriverAdapterCPDS cpds = new DriverAdapterCPDS();

+

+        try

+        {

+			cpds.setDriver(config.getDriverClassName());

+		}

+        catch (ClassNotFoundException e)

+        {

+			throw new SQLException("Driver class not found " + config.getDriverClassName(), e);

+		}

+

+        cpds.setUrl(config.getUrl());

+        cpds.setUser(config.getUserName());

+        cpds.setPassword(config.getPassword());

+

+        return cpds;

+    }

+

+    /**

+     * Initializes the Jdbc2PoolDataSource.

+     *

+     * @param dataSource the dataSource to initialize, not null.

+     * @param config where to read the settings from, not null.

+     *

+     * @throws SQLException if a property set fails.

+     */

+    private void initJdbc2Pool(final InstanceKeyDataSource dataSource, final JDBCDiskCacheAttributes config)

+        throws SQLException

+    {

+        log.debug("Starting initJdbc2Pool");

+

+        dataSource.setDescription(config.getConnectionPoolName());

+    }

+}

diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/hsql/HSQLDiskCacheFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/hsql/HSQLDiskCacheFactory.java
new file mode 100644
index 0000000..42fe5b5
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/hsql/HSQLDiskCacheFactory.java
@@ -0,0 +1,129 @@
+package org.apache.commons.jcs3.auxiliary.disk.jdbc.hsql;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import javax.sql.DataSource;
+
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.JDBCDiskCache;
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.JDBCDiskCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.JDBCDiskCacheFactory;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * This factory should create hsql disk caches.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class HSQLDiskCacheFactory
+    extends JDBCDiskCacheFactory
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog( HSQLDiskCacheFactory.class );
+
+    /**
+     * This factory method should create an instance of the hsqlcache.
+     * <p>
+     * @param rawAttr
+     * @param compositeCacheManager
+     * @param cacheEventLogger
+     * @param elementSerializer
+     * @return JDBCDiskCache
+     * @throws SQLException if the creation of the cache instance fails
+     */
+    @Override
+    public <K, V> JDBCDiskCache<K, V> createCache( AuxiliaryCacheAttributes rawAttr,
+			ICompositeCacheManager compositeCacheManager,
+			ICacheEventLogger cacheEventLogger,
+			IElementSerializer elementSerializer )
+			throws SQLException
+    {
+        // TODO get this from the attributes.
+        System.setProperty( "hsqldb.cache_scale", "8" );
+
+        JDBCDiskCache<K, V> cache = super.createCache(rawAttr, compositeCacheManager,
+                cacheEventLogger, elementSerializer);
+        setupDatabase( cache.getDataSource(), (JDBCDiskCacheAttributes) rawAttr );
+
+        return cache;
+    }
+
+    /**
+     * Creates the table if it doesn't exist
+     * <p>
+     * @param ds Data Source
+     * @param attributes Cache region configuration
+     * @throws SQLException
+     */
+    protected void setupDatabase( DataSource ds, JDBCDiskCacheAttributes attributes )
+        throws SQLException
+    {
+        try (Connection cConn = ds.getConnection())
+        {
+            setupTable( cConn, attributes.getTableName() );
+            log.info( "Finished setting up table [{0}]", attributes.getTableName());
+        }
+    }
+
+    /**
+     * SETUP TABLE FOR CACHE
+     * <p>
+     * @param cConn
+     * @param tableName
+     */
+    protected synchronized void setupTable( Connection cConn, String tableName ) throws SQLException
+    {
+        DatabaseMetaData dmd = cConn.getMetaData();
+        ResultSet result = dmd.getTables(null, null, tableName, null);
+
+        if (!result.next())
+        {
+            // TODO make the cached nature of the table configurable
+            StringBuilder createSql = new StringBuilder();
+            createSql.append( "CREATE CACHED TABLE ").append( tableName );
+            createSql.append( "( " );
+            createSql.append( "CACHE_KEY             VARCHAR(250)          NOT NULL, " );
+            createSql.append( "REGION                VARCHAR(250)          NOT NULL, " );
+            createSql.append( "ELEMENT               BINARY, " );
+            createSql.append( "CREATE_TIME           TIMESTAMP, " );
+            createSql.append( "UPDATE_TIME_SECONDS   BIGINT, " );
+            createSql.append( "MAX_LIFE_SECONDS      BIGINT, " );
+            createSql.append( "SYSTEM_EXPIRE_TIME_SECONDS      BIGINT, " );
+            createSql.append( "IS_ETERNAL            CHAR(1), " );
+            createSql.append( "PRIMARY KEY (CACHE_KEY, REGION) " );
+            createSql.append( ");" );
+
+            try (Statement sStatement = cConn.createStatement())
+            {
+                sStatement.execute( createSql.toString() );
+            }
+        }
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/mysql/MySQLDiskCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/mysql/MySQLDiskCache.java
new file mode 100644
index 0000000..59764dc
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/mysql/MySQLDiskCache.java
@@ -0,0 +1,165 @@
+package org.apache.commons.jcs3.auxiliary.disk.jdbc.mysql;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.sql.SQLException;
+import java.util.Map;
+
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.JDBCDiskCache;
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.TableState;
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.dsfactory.DataSourceFactory;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * The MySQLDiskCache extends the core JDBCDiskCache.
+ * <p>
+ * Although the generic JDBC Disk Cache can be used for MySQL, the MySQL JDBC Disk Cache has
+ * additional features, such as table optimization that are particular to MySQL.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class MySQLDiskCache<K, V>
+	extends JDBCDiskCache<K, V>
+{
+    /** local logger */
+    private static final Log log = LogManager.getLog( MySQLDiskCache.class );
+
+    /** config attributes */
+    private final MySQLDiskCacheAttributes mySQLDiskCacheAttributes;
+
+    /**
+     * Delegates to the super and makes use of the MySQL specific parameters used for scheduled
+     * optimization.
+     * <p>
+     * @param attributes the configuration object for this cache
+     * @param dsFactory the DataSourceFactory for this cache
+     * @param tableState an object to track table operations
+     * @param compositeCacheManager the global cache manager
+     * @throws SQLException if the pool access could not be set up
+     */
+    public MySQLDiskCache( MySQLDiskCacheAttributes attributes, DataSourceFactory dsFactory,
+    		TableState tableState, ICompositeCacheManager compositeCacheManager ) throws SQLException
+    {
+        super( attributes, dsFactory, tableState, compositeCacheManager );
+
+        mySQLDiskCacheAttributes = attributes;
+
+        log.debug( "MySQLDiskCacheAttributes = {0}", attributes );
+    }
+
+    /**
+     * This delegates to the generic JDBC disk cache. If we are currently optimizing, then this
+     * method will balk and return null.
+     * <p>
+     * @param key Key to locate value for.
+     * @return An object matching key, or null.
+     */
+    @Override
+    protected ICacheElement<K, V> processGet( K key )
+    {
+        if ( this.getTableState().getState() == TableState.OPTIMIZATION_RUNNING )
+        {
+            if ( this.mySQLDiskCacheAttributes.isBalkDuringOptimization() )
+            {
+                return null;
+            }
+        }
+        return super.processGet( key );
+    }
+
+    /**
+     * This delegates to the generic JDBC disk cache. If we are currently optimizing, then this
+     * method will balk and return null.
+     * <p>
+     * @param pattern used for like query.
+     * @return An object matching key, or null.
+     */
+    @Override
+    protected Map<K, ICacheElement<K, V>> processGetMatching( String pattern )
+    {
+        if ( this.getTableState().getState() == TableState.OPTIMIZATION_RUNNING )
+        {
+            if ( this.mySQLDiskCacheAttributes.isBalkDuringOptimization() )
+            {
+                return null;
+            }
+        }
+        return super.processGetMatching( pattern );
+    }
+
+    /**
+     * @param pattern
+     * @return String to use in the like query.
+     */
+    @Override
+    public String constructLikeParameterFromPattern( String pattern )
+    {
+        String likePattern = pattern.replaceAll( "\\.\\+", "%" );
+        likePattern = likePattern.replaceAll( "\\.", "_" );
+
+        log.debug( "pattern = [{0}]", likePattern );
+
+        return likePattern;
+    }
+
+    /**
+     * This delegates to the generic JDBC disk cache. If we are currently optimizing, then this
+     * method will balk and do nothing.
+     * <p>
+     * @param element
+     */
+    @Override
+    protected void processUpdate( ICacheElement<K, V> element )
+    {
+        if ( this.getTableState().getState() == TableState.OPTIMIZATION_RUNNING )
+        {
+            if ( this.mySQLDiskCacheAttributes.isBalkDuringOptimization() )
+            {
+                return;
+            }
+        }
+        super.processUpdate( element );
+    }
+
+    /**
+     * Removed the expired. (now - create time) &gt; max life seconds * 1000
+     * <p>
+     * If we are currently optimizing, then this method will balk and do nothing.
+     * <p>
+     * TODO consider blocking and trying again.
+     * <p>
+     * @return the number deleted
+     */
+    @Override
+    protected int deleteExpired()
+    {
+        if ( this.getTableState().getState() == TableState.OPTIMIZATION_RUNNING )
+        {
+            if ( this.mySQLDiskCacheAttributes.isBalkDuringOptimization() )
+            {
+                return -1;
+            }
+        }
+        return super.deleteExpired();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/mysql/MySQLDiskCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/mysql/MySQLDiskCacheAttributes.java
new file mode 100644
index 0000000..854a932
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/mysql/MySQLDiskCacheAttributes.java
@@ -0,0 +1,107 @@
+package org.apache.commons.jcs3.auxiliary.disk.jdbc.mysql;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.JDBCDiskCacheAttributes;
+
+/**
+ * This has additional attributes that are particular to the MySQL disk cache.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class MySQLDiskCacheAttributes
+    extends JDBCDiskCacheAttributes
+{
+    /** Don't change. */
+    private static final long serialVersionUID = -6535808344813320061L;
+
+    /**
+     * For now this is a simple comma delimited list of HH:MM:SS times to optimize
+     * the table. If none is supplied, then no optimizations will be performed.
+     * <p>
+     * In the future we can add a chron like scheduling system. This is to meet
+     * a pressing current need.
+     * <p>
+     * 03:01,15:00 will cause the optimizer to run at 3 am and at 3 pm.
+     */
+    private String optimizationSchedule = null;
+
+    /**
+     * If true, we will balk, that is return null during optimization rather than block.
+     */
+    public static final boolean DEFAULT_BALK_DURING_OPTIMIZATION = true;
+
+    /**
+     * If true, we will balk, that is return null during optimization rather than block.
+     * <p>
+     * <a href="http://en.wikipedia.org/wiki/Balking_pattern">Balking</a>
+     */
+    private boolean balkDuringOptimization = DEFAULT_BALK_DURING_OPTIMIZATION;
+
+    /**
+     * @param optimizationSchedule The optimizationSchedule to set.
+     */
+    public void setOptimizationSchedule( String optimizationSchedule )
+    {
+        this.optimizationSchedule = optimizationSchedule;
+    }
+
+    /**
+     * @return Returns the optimizationSchedule.
+     */
+    public String getOptimizationSchedule()
+    {
+        return optimizationSchedule;
+    }
+
+    /**
+     * @param balkDuringOptimization The balkDuringOptimization to set.
+     */
+    public void setBalkDuringOptimization( boolean balkDuringOptimization )
+    {
+        this.balkDuringOptimization = balkDuringOptimization;
+    }
+
+    /**
+     * Should we return null while optimizing the table.
+     * <p>
+     * @return Returns the balkDuringOptimization.
+     */
+    public boolean isBalkDuringOptimization()
+    {
+        return balkDuringOptimization;
+    }
+
+    /**
+     * For debugging.
+     * <p>
+     * @return debug string
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        buf.append( "\nMySQLDiskCacheAttributes" );
+        buf.append( "\n OptimizationSchedule [" + getOptimizationSchedule() + "]" );
+        buf.append( "\n BalkDuringOptimization [" + isBalkDuringOptimization() + "]" );
+        buf.append( super.toString() );
+        return buf.toString();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/mysql/MySQLDiskCacheFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/mysql/MySQLDiskCacheFactory.java
new file mode 100644
index 0000000..da702ad
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/mysql/MySQLDiskCacheFactory.java
@@ -0,0 +1,162 @@
+package org.apache.commons.jcs3.auxiliary.disk.jdbc.mysql;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.sql.SQLException;
+import java.text.ParseException;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+
+import javax.sql.DataSource;
+
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.JDBCDiskCacheFactory;
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.TableState;
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.dsfactory.DataSourceFactory;
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.mysql.util.ScheduleParser;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * This factory should create mysql disk caches.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class MySQLDiskCacheFactory
+    extends JDBCDiskCacheFactory
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog( MySQLDiskCacheFactory.class );
+
+    /**
+     * This factory method should create an instance of the mysqlcache.
+     * <p>
+     * @param rawAttr specific cache configuration attributes
+     * @param compositeCacheManager the global cache manager
+     * @param cacheEventLogger a specific logger for cache events
+     * @param elementSerializer a serializer for cache elements
+     * @return MySQLDiskCache the cache instance
+     * @throws SQLException if the cache instance could not be created
+     */
+    @Override
+    public <K, V> MySQLDiskCache<K, V> createCache( AuxiliaryCacheAttributes rawAttr,
+            ICompositeCacheManager compositeCacheManager,
+            ICacheEventLogger cacheEventLogger, IElementSerializer elementSerializer )
+            throws SQLException
+    {
+        MySQLDiskCacheAttributes cattr = (MySQLDiskCacheAttributes) rawAttr;
+        TableState tableState = getTableState( cattr.getTableName() );
+        DataSourceFactory dsFactory = getDataSourceFactory(cattr, compositeCacheManager.getConfigurationProperties());
+
+        MySQLDiskCache<K, V> cache = new MySQLDiskCache<>( cattr, dsFactory, tableState, compositeCacheManager );
+        cache.setCacheEventLogger( cacheEventLogger );
+        cache.setElementSerializer( elementSerializer );
+
+        // create a shrinker if we need it.
+        createShrinkerWhenNeeded( cattr, cache );
+        scheduleOptimizations( cattr, tableState, cache.getDataSource() );
+
+        return cache;
+
+    }
+
+    /**
+     * For each time in the optimization schedule, this calls schedule Optimization.
+     * <p>
+     * @param attributes configuration properties.
+     * @param tableState for noting optimization in progress, etc.
+     * @param ds the DataSource
+     */
+    protected void scheduleOptimizations( MySQLDiskCacheAttributes attributes, TableState tableState, DataSource ds  )
+    {
+        if ( attributes != null )
+        {
+            if ( attributes.getOptimizationSchedule() != null )
+            {
+                log.info( "Will try to configure optimization for table [{0}] on schedule [{1}]",
+                        () -> attributes.getTableName(),  () -> attributes.getOptimizationSchedule());
+
+                MySQLTableOptimizer optimizer = new MySQLTableOptimizer( attributes, tableState, ds );
+
+                // loop through the dates.
+                try
+                {
+                    Date[] dates = ScheduleParser.createDatesForSchedule( attributes.getOptimizationSchedule() );
+                    if ( dates != null )
+                    {
+                        for ( int i = 0; i < dates.length; i++ )
+                        {
+                            this.scheduleOptimization( dates[i], optimizer );
+                        }
+                    }
+                }
+                catch ( ParseException e )
+                {
+                    log.warn( "Problem creating optimization schedule for table [{0}]",
+                            attributes.getTableName(), e );
+                }
+            }
+            else
+            {
+                log.info( "Optimization is not configured for table [{0}]",
+                        attributes.getTableName());
+            }
+        }
+    }
+
+    /**
+     * This takes in a single time and schedules the optimizer to be called at that time every day.
+     * <p>
+     * @param startTime -- HH:MM:SS format
+     * @param optimizer
+     */
+    protected void scheduleOptimization( Date startTime, MySQLTableOptimizer optimizer )
+    {
+        log.info( "startTime [{0}] for optimizer {1}", startTime, optimizer );
+
+        Date now = new Date();
+        long initialDelay = startTime.getTime() - now.getTime();
+
+        // have the daemon execute the optimization
+        getScheduledExecutorService().scheduleAtFixedRate(() -> optimizeTable(optimizer),
+                initialDelay, 86400L, TimeUnit.SECONDS );
+    }
+
+    /**
+     * This calls the optimizers' optimize table method. This is used by the timer.
+     * <p>
+     * @author Aaron Smuts
+     */
+    private void optimizeTable(MySQLTableOptimizer optimizer)
+    {
+        if ( optimizer != null )
+        {
+            boolean success = optimizer.optimizeTable();
+            log.info( "Optimization success status [{0}]", success );
+        }
+        else
+        {
+            log.warn( "OptimizerRunner: The optimizer is null. Could not optimize table." );
+        }
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/mysql/MySQLTableOptimizer.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/mysql/MySQLTableOptimizer.java
new file mode 100644
index 0000000..dc83d54
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/mysql/MySQLTableOptimizer.java
@@ -0,0 +1,278 @@
+package org.apache.commons.jcs3.auxiliary.disk.jdbc.mysql;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import javax.sql.DataSource;
+
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.TableState;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.timing.ElapsedTimer;
+
+/**
+ * The MySQL Table Optimizer can optimize MySQL tables. It knows how to optimize for MySQL databases
+ * in particular and how to repair the table if it is corrupted in the process.
+ * <p>
+ * We will probably be able to abstract out a generic optimizer interface from this class in the
+ * future.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class MySQLTableOptimizer
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog( MySQLTableOptimizer.class );
+
+    /** The data source */
+    private DataSource dataSource = null;
+
+    /** The name of the table. */
+    private String tableName = null;
+
+    /** optimizing, etc. */
+    private final TableState tableState;
+
+    /**
+     * This constructs an optimizer with the disk cacn properties.
+     * <p>
+     * @param attributes
+     * @param tableState We mark the table status as optimizing when this is happening.
+     * @param dataSource access to the database
+     */
+    public MySQLTableOptimizer( MySQLDiskCacheAttributes attributes, TableState tableState, DataSource dataSource )
+    {
+        setTableName( attributes.getTableName() );
+
+        this.tableState = tableState;
+        this.dataSource = dataSource;
+    }
+
+    /**
+     * A scheduler will call this method. When it is called the table state is marked as optimizing.
+     * TODO we need to verify that no deletions are running before we call optimize. We should wait
+     * if a deletion is in progress.
+     * <p>
+     * This restores when there is an optimization error. The error output looks like this:
+     *
+     * <pre>
+     *           mysql&gt; optimize table JCS_STORE_FLIGHT_OPTION_ITINERARY;
+     *               +---------------------------------------------+----------+----------+---------------------+
+     *               | Table                                       | Op       | Msg_type | Msg_text            |
+     *               +---------------------------------------------+----------+----------+---------------------+
+     *               | jcs_cache.JCS_STORE_FLIGHT_OPTION_ITINERARY | optimize | error    | 2 when fixing table |
+     *               | jcs_cache.JCS_STORE_FLIGHT_OPTION_ITINERARY | optimize | status   | Operation failed    |
+     *               +---------------------------------------------+----------+----------+---------------------+
+     *               2 rows in set (51.78 sec)
+     * </pre>
+     *
+     * A successful repair response looks like this:
+     *
+     * <pre>
+     *        mysql&gt; REPAIR TABLE JCS_STORE_FLIGHT_OPTION_ITINERARY;
+     *            +---------------------------------------------+--------+----------+----------------------------------------------+
+     *            | Table                                       | Op     | Msg_type | Msg_text                                     |
+     *            +---------------------------------------------+--------+----------+----------------------------------------------+
+     *            | jcs_cache.JCS_STORE_FLIGHT_OPTION_ITINERARY | repair | error    | 2 when fixing table                          |
+     *            | jcs_cache.JCS_STORE_FLIGHT_OPTION_ITINERARY | repair | warning  | Number of rows changed from 131276 to 260461 |
+     *            | jcs_cache.JCS_STORE_FLIGHT_OPTION_ITINERARY | repair | status   | OK                                           |
+     *            +---------------------------------------------+--------+----------+----------------------------------------------+
+     *            3 rows in set (3 min 5.94 sec)
+     * </pre>
+     *
+     * A successful optimization looks like this:
+     *
+     * <pre>
+     *       mysql&gt; optimize table JCS_STORE_DEFAULT;
+     *           +-----------------------------+----------+----------+----------+
+     *           | Table                       | Op       | Msg_type | Msg_text |
+     *           +-----------------------------+----------+----------+----------+
+     *           | jcs_cache.JCS_STORE_DEFAULT | optimize | status   | OK       |
+     *           +-----------------------------+----------+----------+----------+
+     *           1 row in set (1.10 sec)
+     * </pre>
+     * @return true if it worked
+     */
+    public boolean optimizeTable()
+    {
+        ElapsedTimer timer = new ElapsedTimer();
+        boolean success = false;
+
+        if ( tableState.getState() == TableState.OPTIMIZATION_RUNNING )
+        {
+            log.warn( "Skipping optimization. Optimize was called, but the "
+                    + "table state indicates that an optimization is currently running." );
+            return false;
+        }
+
+        try
+        {
+            tableState.setState( TableState.OPTIMIZATION_RUNNING );
+            log.info( "Optimizing table [{0}]", this.getTableName());
+
+            try (Connection con = dataSource.getConnection())
+            {
+                // TEST
+
+                try (Statement sStatement = con.createStatement())
+                {
+                    ResultSet rs = sStatement.executeQuery( "optimize table " + this.getTableName() );
+
+                    // first row is error, then status
+                    // if there is only one row in the result set, everything
+                    // should be fine.
+                    // This may be mysql version specific.
+                    if ( rs.next() )
+                    {
+                        String status = rs.getString( "Msg_type" );
+                        String message = rs.getString( "Msg_text" );
+
+                        log.info( "Message Type: {0}", status );
+                        log.info( "Message: {0}", message );
+
+                        if ( "error".equals( status ) )
+                        {
+                            log.warn( "Optimization was in error. Will attempt "
+                                    + "to repair the table. Message: {0}", message);
+
+                            // try to repair the table.
+                            success = repairTable( sStatement );
+                        }
+                        else
+                        {
+                            success = true;
+                        }
+                    }
+
+                    // log the table status
+                    String statusString = getTableStatus( sStatement );
+                    log.info( "Table status after optimizing table [{0}]: {1}",
+                            this.getTableName(), statusString );
+                }
+                catch ( SQLException e )
+                {
+                    log.error( "Problem optimizing table [{0}]",
+                            this.getTableName(), e );
+                    return false;
+                }
+            }
+            catch ( SQLException e )
+            {
+                log.error( "Problem getting connection.", e );
+            }
+        }
+        finally
+        {
+            tableState.setState( TableState.FREE );
+
+            log.info( "Optimization of table [{0}] took {1} ms.",
+                    () -> this.getTableName(), () -> timer.getElapsedTime() );
+        }
+
+        return success;
+    }
+
+    /**
+     * This calls show table status and returns the result as a String.
+     * <p>
+     * @param sStatement
+     * @return String
+     * @throws SQLException
+     */
+    protected String getTableStatus( Statement sStatement )
+        throws SQLException
+    {
+        ResultSet statusResultSet = sStatement.executeQuery( "show table status" );
+        StringBuilder statusString = new StringBuilder();
+        int numColumns = statusResultSet.getMetaData().getColumnCount();
+        while ( statusResultSet.next() )
+        {
+            statusString.append( "\n" );
+            for ( int i = 1; i <= numColumns; i++ )
+            {
+                statusString.append( statusResultSet.getMetaData().getColumnLabel( i ) + " ["
+                    + statusResultSet.getString( i ) + "]  |  " );
+            }
+        }
+        return statusString.toString();
+    }
+
+    /**
+     * This is called if the optimization is in error.
+     * <p>
+     * It looks for "OK" in response. If it find "OK" as a message in any result set row, it returns
+     * true. Otherwise we assume that the repair failed.
+     * <p>
+     * @param sStatement
+     * @return true if successful
+     * @throws SQLException
+     */
+    protected boolean repairTable( Statement sStatement )
+        throws SQLException
+    {
+        boolean success = false;
+
+        // if( message != null && message.indexOf( ) )
+        ResultSet repairResult = sStatement.executeQuery( "repair table " + this.getTableName() );
+        StringBuilder repairString = new StringBuilder();
+        int numColumns = repairResult.getMetaData().getColumnCount();
+        while ( repairResult.next() )
+        {
+            for ( int i = 1; i <= numColumns; i++ )
+            {
+                repairString.append( repairResult.getMetaData().getColumnLabel( i ) + " [" + repairResult.getString( i )
+                    + "]  |  " );
+            }
+
+            String message = repairResult.getString( "Msg_text" );
+            if ( "OK".equals( message ) )
+            {
+                success = true;
+            }
+        }
+        log.info("{0}", repairString);
+
+        if ( !success )
+        {
+            log.warn( "Failed to repair the table. {0}", repairString );
+        }
+        return success;
+    }
+
+    /**
+     * @param tableName The tableName to set.
+     */
+    public void setTableName( String tableName )
+    {
+        this.tableName = tableName;
+    }
+
+    /**
+     * @return Returns the tableName.
+     */
+    public String getTableName()
+    {
+        return tableName;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/mysql/util/ScheduleParser.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/mysql/util/ScheduleParser.java
new file mode 100644
index 0000000..11e87b9
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/mysql/util/ScheduleParser.java
@@ -0,0 +1,96 @@
+package org.apache.commons.jcs3.auxiliary.disk.jdbc.mysql.util;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.StringTokenizer;
+
+/**
+ * Parses the very simple schedule format.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class ScheduleParser
+{
+    /**
+     * For each date time that is separated by a comma in the
+     * OptimizationSchedule, create a date and add it to an array of dates.
+     * <p>
+     * @param schedule
+     * @return Date[]
+     * @throws ParseException
+     */
+    public static Date[] createDatesForSchedule( String schedule )
+        throws ParseException
+    {
+        if ( schedule == null )
+        {
+            throw new ParseException( "Cannot create schedules for a null String.", 0 );
+        }
+
+        StringTokenizer toker = new StringTokenizer( schedule, "," );
+        Date[] dates = new Date[toker.countTokens()];
+        int cnt = 0;
+        while ( toker.hasMoreTokens() )
+        {
+            String time = toker.nextToken();
+            dates[cnt] = getDateForSchedule( time );
+            cnt++;
+        }
+        return dates;
+    }
+
+    /**
+     * For a single string it creates a date that is the next time this hh:mm:ss
+     * combo will be seen.
+     * <p>
+     * @param startTime
+     * @return Date
+     * @throws ParseException
+     */
+    public static Date getDateForSchedule( String startTime )
+        throws ParseException
+    {
+        if ( startTime == null )
+        {
+            throw new ParseException( "Cannot create date for a null String.", 0 );
+        }
+
+        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
+        Date date = sdf.parse(startTime);
+        Calendar cal = Calendar.getInstance();
+        // This will result in a date of 1/1/1970
+        cal.setTime(date);
+
+        Calendar now = Calendar.getInstance();
+        cal.set(now.get(Calendar.YEAR), now.get(Calendar.MONTH), now.get(Calendar.DAY_OF_MONTH));
+
+        // if the date is less than now, add a day.
+        if ( cal.before( now ) )
+        {
+            cal.add( Calendar.DAY_OF_MONTH, 1 );
+        }
+
+        return cal.getTime();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/package.html b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/package.html
similarity index 100%
rename from commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/disk/package.html
rename to commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/package.html
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/LateralCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/LateralCache.java
new file mode 100644
index 0000000..433ee5e
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/LateralCache.java
@@ -0,0 +1,417 @@
+package org.apache.commons.jcs3.auxiliary.lateral;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCacheEventLogging;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.lateral.behavior.ILateralCacheAttributes;
+import org.apache.commons.jcs3.engine.CacheInfo;
+import org.apache.commons.jcs3.engine.CacheStatus;
+import org.apache.commons.jcs3.engine.ZombieCacheServiceNonLocal;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheServiceNonLocal;
+import org.apache.commons.jcs3.engine.behavior.IZombie;
+import org.apache.commons.jcs3.engine.stats.Stats;
+import org.apache.commons.jcs3.engine.stats.behavior.IStats;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * Lateral distributor. Returns null on get by default. Net search not implemented.
+ */
+public class LateralCache<K, V>
+    extends AbstractAuxiliaryCacheEventLogging<K, V>
+{
+    /** The logger. */
+    private static final Log log = LogManager.getLog( LateralCache.class );
+
+    /** generalize this, use another interface */
+    private final ILateralCacheAttributes lateralCacheAttributes;
+
+    /** The region name */
+    final String cacheName;
+
+    /** either http, socket.udp, or socket.tcp can set in config */
+    private ICacheServiceNonLocal<K, V> lateralCacheService;
+
+    /** Monitors the connection. */
+    private LateralCacheMonitor monitor;
+
+    /**
+     * Constructor for the LateralCache object
+     * <p>
+     * @param cattr
+     * @param lateral
+     * @param monitor
+     */
+    public LateralCache( ILateralCacheAttributes cattr, ICacheServiceNonLocal<K, V> lateral, LateralCacheMonitor monitor )
+    {
+        this.cacheName = cattr.getCacheName();
+        this.lateralCacheAttributes = cattr;
+        this.lateralCacheService = lateral;
+        this.monitor = monitor;
+    }
+
+    /**
+     * Constructor for the LateralCache object
+     * <p>
+     * @param cattr
+     */
+    public LateralCache( ILateralCacheAttributes cattr )
+    {
+        this.cacheName = cattr.getCacheName();
+        this.lateralCacheAttributes = cattr;
+    }
+
+    /**
+     * Update lateral.
+     * <p>
+     * @param ce
+     * @throws IOException
+     */
+    @Override
+    protected void processUpdate( ICacheElement<K, V> ce )
+        throws IOException
+    {
+        try
+        {
+            if (ce != null)
+            {
+                log.debug( "update: lateral = [{0}], CacheInfo.listenerId = {1}",
+                        lateralCacheService, CacheInfo.listenerId );
+                lateralCacheService.update( ce, CacheInfo.listenerId );
+            }
+        }
+        catch ( IOException ex )
+        {
+            handleException( ex, "Failed to put [" + ce.getKey() + "] to " + ce.getCacheName() + "@" + lateralCacheAttributes );
+        }
+    }
+
+    /**
+     * The performance costs are too great. It is not recommended that you enable lateral gets.
+     * <p>
+     * @param key
+     * @return ICacheElement&lt;K, V&gt; or null
+     * @throws IOException
+     */
+    @Override
+    protected ICacheElement<K, V> processGet( K key )
+        throws IOException
+    {
+        ICacheElement<K, V> obj = null;
+
+        if ( this.lateralCacheAttributes.getPutOnlyMode() )
+        {
+            return null;
+        }
+        try
+        {
+            obj = lateralCacheService.get( cacheName, key );
+        }
+        catch ( Exception e )
+        {
+            log.error( e );
+            handleException( e, "Failed to get [" + key + "] from " + lateralCacheAttributes.getCacheName() + "@" + lateralCacheAttributes );
+        }
+        return obj;
+    }
+
+    /**
+     * @param pattern
+     * @return A map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache for any of these keys
+     * @throws IOException
+     */
+    @Override
+    protected Map<K, ICacheElement<K, V>> processGetMatching( String pattern )
+        throws IOException
+    {
+        if ( this.lateralCacheAttributes.getPutOnlyMode() )
+        {
+            return Collections.emptyMap();
+        }
+        try
+        {
+            return lateralCacheService.getMatching( cacheName, pattern );
+        }
+        catch ( IOException e )
+        {
+            log.error( e );
+            handleException( e, "Failed to getMatching [" + pattern + "] from " + lateralCacheAttributes.getCacheName() + "@" + lateralCacheAttributes );
+            return Collections.emptyMap();
+        }
+    }
+
+    /**
+     * Return the keys in this cache.
+     * <p>
+     * @see org.apache.commons.jcs3.auxiliary.AuxiliaryCache#getKeySet()
+     */
+    @Override
+    public Set<K> getKeySet() throws IOException
+    {
+        try
+        {
+            return lateralCacheService.getKeySet( cacheName );
+        }
+        catch ( IOException ex )
+        {
+            handleException( ex, "Failed to get key set from " + lateralCacheAttributes.getCacheName() + "@"
+                + lateralCacheAttributes );
+        }
+        return Collections.emptySet();
+    }
+
+    /**
+     * Synchronously remove from the remote cache; if failed, replace the remote handle with a
+     * zombie.
+     * <p>
+     * @param key
+     * @return false always
+     * @throws IOException
+     */
+    @Override
+    protected boolean processRemove( K key )
+        throws IOException
+    {
+        log.debug( "removing key: {0}", key );
+
+        try
+        {
+            lateralCacheService.remove( cacheName, key, CacheInfo.listenerId );
+        }
+        catch ( IOException ex )
+        {
+            handleException( ex, "Failed to remove " + key + " from " + lateralCacheAttributes.getCacheName() + "@" + lateralCacheAttributes );
+        }
+        return false;
+    }
+
+    /**
+     * Synchronously removeAll from the remote cache; if failed, replace the remote handle with a
+     * zombie.
+     * <p>
+     * @throws IOException
+     */
+    @Override
+    protected void processRemoveAll()
+        throws IOException
+    {
+        try
+        {
+            lateralCacheService.removeAll( cacheName, CacheInfo.listenerId );
+        }
+        catch ( IOException ex )
+        {
+            handleException( ex, "Failed to remove all from " + lateralCacheAttributes.getCacheName() + "@" + lateralCacheAttributes );
+        }
+    }
+
+    /**
+     * Synchronously dispose the cache. Not sure we want this.
+     * <p>
+     * @throws IOException
+     */
+    @Override
+    protected void processDispose()
+        throws IOException
+    {
+        log.debug( "Disposing of lateral cache" );
+
+        ///* HELP: This section did nothing but generate compilation warnings.
+        // TODO: may limit this functionality. It is dangerous.
+        // asmuts -- Added functionality to help with warnings. I'm not getting
+        // any.
+        try
+        {
+            lateralCacheService.dispose( this.lateralCacheAttributes.getCacheName() );
+            // Should remove connection
+        }
+        catch ( IOException ex )
+        {
+            log.error( "Couldn't dispose", ex );
+            handleException( ex, "Failed to dispose " + lateralCacheAttributes.getCacheName() );
+        }
+    }
+
+    /**
+     * Returns the cache status.
+     * <p>
+     * @return The status value
+     */
+    @Override
+    public CacheStatus getStatus()
+    {
+        return this.lateralCacheService instanceof IZombie ? CacheStatus.ERROR : CacheStatus.ALIVE;
+    }
+
+    /**
+     * Returns the current cache size.
+     * <p>
+     * @return The size value
+     */
+    @Override
+    public int getSize()
+    {
+        return 0;
+    }
+
+    /**
+     * Gets the cacheType attribute of the LateralCache object
+     * <p>
+     * @return The cacheType value
+     */
+    @Override
+    public CacheType getCacheType()
+    {
+        return CacheType.LATERAL_CACHE;
+    }
+
+    /**
+     * Gets the cacheName attribute of the LateralCache object
+     * <p>
+     * @return The cacheName value
+     */
+    @Override
+    public String getCacheName()
+    {
+        return cacheName;
+    }
+
+    /**
+     * Not yet sure what to do here.
+     * <p>
+     * @param ex
+     * @param msg
+     * @throws IOException
+     */
+    private void handleException( Exception ex, String msg )
+        throws IOException
+    {
+        log.error( "Disabling lateral cache due to error {0}", msg, ex );
+
+        lateralCacheService = new ZombieCacheServiceNonLocal<>( lateralCacheAttributes.getZombieQueueMaxSize() );
+        // may want to flush if region specifies
+        // Notify the cache monitor about the error, and kick off the recovery
+        // process.
+        monitor.notifyError();
+
+        // could stop the net search if it is built and try to reconnect?
+        if ( ex instanceof IOException )
+        {
+            throw (IOException) ex;
+        }
+        throw new IOException( ex.getMessage() );
+    }
+
+    /**
+     * Replaces the current remote cache service handle with the given handle.
+     * <p>
+     * @param restoredLateral
+     */
+    public void fixCache( ICacheServiceNonLocal<K, V> restoredLateral )
+    {
+        if ( this.lateralCacheService != null && this.lateralCacheService instanceof ZombieCacheServiceNonLocal )
+        {
+            ZombieCacheServiceNonLocal<K, V> zombie = (ZombieCacheServiceNonLocal<K, V>) this.lateralCacheService;
+            this.lateralCacheService = restoredLateral;
+            try
+            {
+                zombie.propagateEvents( restoredLateral );
+            }
+            catch ( Exception e )
+            {
+                try
+                {
+                    handleException( e, "Problem propagating events from Zombie Queue to new Lateral Service." );
+                }
+                catch ( IOException e1 )
+                {
+                    // swallow, since this is just expected kick back.  Handle always throws
+                }
+            }
+        }
+        else
+        {
+            this.lateralCacheService = restoredLateral;
+        }
+    }
+
+    /**
+     * getStats
+     * <p>
+     * @return String
+     */
+    @Override
+    public String getStats()
+    {
+        return "";
+    }
+
+    /**
+     * @return Returns the AuxiliaryCacheAttributes.
+     */
+    @Override
+    public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
+    {
+        return lateralCacheAttributes;
+    }
+
+    /**
+     * @return debugging data.
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        buf.append( "\n LateralCache " );
+        buf.append( "\n Cache Name [" + lateralCacheAttributes.getCacheName() + "]" );
+        buf.append( "\n cattr =  [" + lateralCacheAttributes + "]" );
+        return buf.toString();
+    }
+
+    /**
+     * @return extra data.
+     */
+    @Override
+    public String getEventLoggingExtraInfo()
+    {
+        return null;
+    }
+
+    /**
+     * The NoWait on top does not call out to here yet.
+     * <p>
+     * @return almost nothing
+     */
+    @Override
+    public IStats getStatistics()
+    {
+        IStats stats = new Stats();
+        stats.setTypeName( "LateralCache" );
+        return stats;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/LateralCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/LateralCacheAttributes.java
new file mode 100644
index 0000000..92e6a35
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/LateralCacheAttributes.java
@@ -0,0 +1,292 @@
+package org.apache.commons.jcs3.auxiliary.lateral;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.lateral.behavior.ILateralCacheAttributes;
+
+/**
+ * This class stores attributes for all of the available lateral cache auxiliaries.
+ */
+public class LateralCacheAttributes
+    extends AbstractAuxiliaryCacheAttributes
+    implements ILateralCacheAttributes
+{
+    /** Don't change */
+    private static final long serialVersionUID = -3408449508837393660L;
+
+    /** Default receive setting */
+    private static final boolean DEFAULT_RECEIVE = true;
+
+    /** THe type of lateral */
+    private String transmissionTypeName = "UDP";
+
+    /** indicates the lateral type, this needs to change */
+    private Type transmissionType = Type.UDP;
+
+    /** The http servers */
+    private String httpServers;
+
+    /** used to identify the service that this manager will be operating on */
+    private String httpServer = "";
+
+    /** this needs to change */
+    private String udpMulticastAddr = "228.5.6.7";
+
+    /** this needs to change */
+    private int udpMulticastPort = 6789;
+
+    /** this needs to change */
+    private int httpListenerPort = 8080;
+
+    /** disables gets from laterals */
+    private boolean putOnlyMode = true;
+
+    /**
+     * do we receive and broadcast or only broadcast this is useful when you don't want to get any
+     * notifications
+     */
+    private boolean receive = DEFAULT_RECEIVE;
+
+    /** If the primary fails, we will queue items before reconnect.  This limits the number of items that can be queued. */
+    private int zombieQueueMaxSize = DEFAULT_ZOMBIE_QUEUE_MAX_SIZE;
+
+    /**
+     * Sets the httpServer attribute of the LateralCacheAttributes object
+     * <P>
+     * @param val The new httpServer value
+     */
+    @Override
+    public void setHttpServer( String val )
+    {
+        httpServer = val;
+    }
+
+    /**
+     * Gets the httpServer attribute of the LateralCacheAttributes object
+     * @return The httpServer value
+     */
+    @Override
+    public String getHttpServer()
+    {
+        return httpServer;
+    }
+
+    /**
+     * Sets the httpServers attribute of the LateralCacheAttributes object
+     * @param val The new httpServers value
+     */
+    @Override
+    public void setHttpServers( String val )
+    {
+        httpServers = val;
+    }
+
+    /**
+     * Gets the httpSrvers attribute of the LateralCacheAttributes object
+     * @return The httpServers value
+     */
+    @Override
+    public String getHttpServers()
+    {
+        return httpServers;
+    }
+
+    /**
+     * Sets the httpListenerPort attribute of the ILateralCacheAttributes object
+     * @param val The new tcpListenerPort value
+     */
+    @Override
+    public void setHttpListenerPort( int val )
+    {
+        this.httpListenerPort = val;
+    }
+
+    /**
+     * Gets the httpListenerPort attribute of the ILateralCacheAttributes object
+     * @return The httpListenerPort value
+     */
+    @Override
+    public int getHttpListenerPort()
+    {
+        return this.httpListenerPort;
+    }
+
+    /**
+     * Sets the udpMulticastAddr attribute of the LateralCacheAttributes object
+     * @param val The new udpMulticastAddr value
+     */
+    @Override
+    public void setUdpMulticastAddr( String val )
+    {
+        udpMulticastAddr = val;
+    }
+
+    /**
+     * Gets the udpMulticastAddr attribute of the LateralCacheAttributes object
+     * @return The udpMulticastAddr value
+     */
+    @Override
+    public String getUdpMulticastAddr()
+    {
+        return udpMulticastAddr;
+    }
+
+    /**
+     * Sets the udpMulticastPort attribute of the LateralCacheAttributes object
+     * @param val The new udpMulticastPort value
+     */
+    @Override
+    public void setUdpMulticastPort( int val )
+    {
+        udpMulticastPort = val;
+    }
+
+    /**
+     * Gets the udpMulticastPort attribute of the LateralCacheAttributes object
+     * @return The udpMulticastPort value
+     */
+    @Override
+    public int getUdpMulticastPort()
+    {
+        return udpMulticastPort;
+    }
+
+    /**
+     * Sets the transmissionType attribute of the LateralCacheAttributes object
+     * @param val The new transmissionType value
+     */
+    @Override
+    public void setTransmissionType( Type val )
+    {
+        this.transmissionType = val;
+        this.transmissionTypeName = val.toString();
+    }
+
+    /**
+     * Gets the transmissionType attribute of the LateralCacheAttributes object
+     * @return The transmissionType value
+     */
+    @Override
+    public Type getTransmissionType()
+    {
+        return this.transmissionType;
+    }
+
+    /**
+     * Sets the transmissionTypeName attribute of the LateralCacheAttributes object
+     * @param val The new transmissionTypeName value
+     */
+    @Override
+    public void setTransmissionTypeName( String val )
+    {
+        this.transmissionTypeName = val;
+        this.transmissionType = Type.valueOf(val);
+    }
+
+    /**
+     * Gets the transmissionTypeName attribute of the LateralCacheAttributes object
+     * @return The transmissionTypeName value
+     */
+    @Override
+    public String getTransmissionTypeName()
+    {
+        return this.transmissionTypeName;
+    }
+
+    /**
+     * Sets the outgoingOnlyMode attribute of the ILateralCacheAttributes. When this is true the
+     * lateral cache will only issue put and remove order and will not try to retrieve elements from
+     * other lateral caches.
+     * @param val The new transmissionTypeName value
+     */
+    @Override
+    public void setPutOnlyMode( boolean val )
+    {
+        this.putOnlyMode = val;
+    }
+
+    /**
+     * @return The outgoingOnlyMode value. Stops gets from going remote.
+     */
+    @Override
+    public boolean getPutOnlyMode()
+    {
+        return putOnlyMode;
+    }
+
+    /**
+     * @param receive The receive to set.
+     */
+    @Override
+    public void setReceive( boolean receive )
+    {
+        this.receive = receive;
+    }
+
+    /**
+     * @return Returns the receive.
+     */
+    @Override
+    public boolean isReceive()
+    {
+        return receive;
+    }
+
+    /**
+     * The number of elements the zombie queue will hold. This queue is used to store events if we
+     * loose our connection with the server.
+     * <p>
+     * @param zombieQueueMaxSize The zombieQueueMaxSize to set.
+     */
+    @Override
+    public void setZombieQueueMaxSize( int zombieQueueMaxSize )
+    {
+        this.zombieQueueMaxSize = zombieQueueMaxSize;
+    }
+
+    /**
+     * The number of elements the zombie queue will hold. This queue is used to store events if we
+     * loose our connection with the server.
+     * <p>
+     * @return Returns the zombieQueueMaxSize.
+     */
+    @Override
+    public int getZombieQueueMaxSize()
+    {
+        return zombieQueueMaxSize;
+    }
+
+    /**
+     * @return debug string.
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        //buf.append( "cacheName=" + cacheName + "\n" );
+        //buf.append( "putOnlyMode=" + putOnlyMode + "\n" );
+        //buf.append( "transmissionTypeName=" + transmissionTypeName + "\n" );
+        //buf.append( "transmissionType=" + transmissionType + "\n" );
+        //buf.append( "tcpServer=" + tcpServer + "\n" );
+        buf.append( transmissionTypeName + httpServer + udpMulticastAddr + String.valueOf( udpMulticastPort ) );
+        return buf.toString();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/LateralCacheMonitor.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/LateralCacheMonitor.java
new file mode 100644
index 0000000..cb8f62a
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/LateralCacheMonitor.java
@@ -0,0 +1,136 @@
+package org.apache.commons.jcs3.auxiliary.lateral;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCacheMonitor;
+import org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.LateralTCPCacheFactory;
+import org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.behavior.ITCPLateralCacheAttributes;
+import org.apache.commons.jcs3.engine.CacheStatus;
+import org.apache.commons.jcs3.engine.ZombieCacheServiceNonLocal;
+import org.apache.commons.jcs3.engine.behavior.ICacheServiceNonLocal;
+
+/**
+ * Used to monitor and repair any failed connection for the lateral cache service. By default the
+ * monitor operates in a failure driven mode. That is, it goes into a wait state until there is an
+ * error. Upon the notification of a connection error, the monitor changes to operate in a time
+ * driven mode. That is, it attempts to recover the connections on a periodic basis. When all failed
+ * connections are restored, it changes back to the failure driven mode.
+ */
+public class LateralCacheMonitor extends AbstractAuxiliaryCacheMonitor
+{
+    /**
+     * Map of caches to monitor
+     */
+    private final ConcurrentHashMap<String, LateralCacheNoWait<?, ?>> caches;
+
+    /**
+     * Reference to the factory
+     */
+    private final LateralTCPCacheFactory factory;
+
+    /**
+     * Allows close classes, ie testers to set the idle period to something testable.
+     * <p>
+     * @param idlePeriod
+     */
+    protected static void forceShortIdlePeriod( long idlePeriod )
+    {
+        LateralCacheMonitor.idlePeriod = idlePeriod;
+    }
+
+    /**
+     * Constructor for the LateralCacheMonitor object
+     * <p>
+     * It's the clients responsibility to decide how many of these there will be.
+     *
+     * @param factory a reference to the factory that manages the service instances
+     */
+    public LateralCacheMonitor(LateralTCPCacheFactory factory)
+    {
+        super("JCS-LateralCacheMonitor");
+        this.factory = factory;
+        this.caches = new ConcurrentHashMap<>();
+        setIdlePeriod(20000L);
+    }
+
+    /**
+     * Add a cache to be monitored
+     *
+     * @param cache the cache
+     */
+    public void addCache(LateralCacheNoWait<?, ?> cache)
+    {
+        this.caches.put(cache.getCacheName(), cache);
+
+        // if not yet started, go ahead
+        if (this.getState() == Thread.State.NEW)
+        {
+            this.start();
+        }
+    }
+
+    /**
+     * Clean up all resources before shutdown
+     */
+    @Override
+    public void dispose()
+    {
+        this.caches.clear();
+    }
+
+    /**
+     * Main processing method for the LateralCacheMonitor object
+     */
+    @Override
+    public void doWork()
+    {
+        // Monitor each cache instance one after the other.
+        log.info( "Number of caches to monitor = " + caches.size() );
+        //for
+        for (Map.Entry<String, LateralCacheNoWait<?, ?>> entry : caches.entrySet())
+        {
+            String cacheName = entry.getKey();
+
+            @SuppressWarnings("unchecked") // Downcast to match service
+            LateralCacheNoWait<Object, Object> c = (LateralCacheNoWait<Object, Object>) entry.getValue();
+            if ( c.getStatus() == CacheStatus.ERROR )
+            {
+                log.info( "Found LateralCacheNoWait in error, " + cacheName );
+
+                ITCPLateralCacheAttributes lca = (ITCPLateralCacheAttributes)c.getAuxiliaryCacheAttributes();
+
+                // Get service instance
+                ICacheServiceNonLocal<Object, Object> cacheService = factory.getCSNLInstance(lca);
+
+                // If we can't fix them, just skip and re-try in the
+                // next round.
+                if (cacheService instanceof ZombieCacheServiceNonLocal)
+                {
+                    continue;
+                }
+
+                c.fixCache(cacheService);
+            }
+        }
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/LateralCacheNoWait.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/LateralCacheNoWait.java
new file mode 100644
index 0000000..b85616b
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/LateralCacheNoWait.java
@@ -0,0 +1,433 @@
+package org.apache.commons.jcs3.auxiliary.lateral;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.rmi.UnmarshalException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCache;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
+import org.apache.commons.jcs3.engine.CacheAdaptor;
+import org.apache.commons.jcs3.engine.CacheEventQueueFactory;
+import org.apache.commons.jcs3.engine.CacheInfo;
+import org.apache.commons.jcs3.engine.CacheStatus;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheEventQueue;
+import org.apache.commons.jcs3.engine.behavior.ICacheServiceNonLocal;
+import org.apache.commons.jcs3.engine.stats.StatElement;
+import org.apache.commons.jcs3.engine.stats.Stats;
+import org.apache.commons.jcs3.engine.stats.behavior.IStatElement;
+import org.apache.commons.jcs3.engine.stats.behavior.IStats;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * Used to queue up update requests to the underlying cache. These requests will be processed in
+ * their order of arrival via the cache event queue processor.
+ */
+public class LateralCacheNoWait<K, V>
+    extends AbstractAuxiliaryCache<K, V>
+{
+    /** The logger. */
+    private static final Log log = LogManager.getLog( LateralCacheNoWait.class );
+
+    /** The cache */
+    private final LateralCache<K, V> cache;
+
+    /** The event queue */
+    private ICacheEventQueue<K, V> eventQueue;
+
+    /** times get called */
+    private int getCount = 0;
+
+    /** times remove called */
+    private int removeCount = 0;
+
+    /** times put called */
+    private int putCount = 0;
+
+    /**
+     * Constructs with the given lateral cache, and fires up an event queue for asynchronous
+     * processing.
+     * <p>
+     * @param cache
+     */
+    public LateralCacheNoWait( LateralCache<K, V> cache )
+    {
+        this.cache = cache;
+
+        log.debug( "Constructing LateralCacheNoWait, LateralCache = [{0}]", cache );
+
+        CacheEventQueueFactory<K, V> fact = new CacheEventQueueFactory<>();
+        this.eventQueue = fact.createCacheEventQueue( new CacheAdaptor<>( cache ), CacheInfo.listenerId, cache
+            .getCacheName(), cache.getAuxiliaryCacheAttributes().getEventQueuePoolName(), cache
+            .getAuxiliaryCacheAttributes().getEventQueueType() );
+
+        // need each no wait to handle each of its real updates and removes,
+        // since there may
+        // be more than one per cache? alternative is to have the cache
+        // perform updates using a different method that specifies the listener
+        // this.q = new CacheEventQueue(new CacheAdaptor(this),
+        // LateralCacheInfo.listenerId, cache.getCacheName());
+        if ( cache.getStatus() == CacheStatus.ERROR )
+        {
+            eventQueue.destroy();
+        }
+    }
+
+    /**
+     * @param ce
+     * @throws IOException
+     */
+    @Override
+    public void update( ICacheElement<K, V> ce )
+        throws IOException
+    {
+        putCount++;
+        try
+        {
+            eventQueue.addPutEvent( ce );
+        }
+        catch ( IOException ex )
+        {
+            log.error( ex );
+            eventQueue.destroy();
+        }
+    }
+
+    /**
+     * Synchronously reads from the lateral cache.
+     * <p>
+     * @param key
+     * @return ICacheElement&lt;K, V&gt; if found, else null
+     */
+    @Override
+    public ICacheElement<K, V> get( K key )
+    {
+        getCount++;
+        if ( this.getStatus() != CacheStatus.ERROR )
+        {
+            try
+            {
+                return cache.get( key );
+            }
+            catch ( UnmarshalException ue )
+            {
+                log.debug( "Retrying the get owing to UnmarshalException..." );
+                try
+                {
+                    return cache.get( key );
+                }
+                catch ( IOException ex )
+                {
+                    log.error( "Failed in retrying the get for the second time." );
+                    eventQueue.destroy();
+                }
+            }
+            catch ( IOException ex )
+            {
+                eventQueue.destroy();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets multiple items from the cache based on the given set of keys.
+     * <p>
+     * @param keys
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache for any of these keys
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMultiple(Set<K> keys)
+    {
+        if ( keys != null && !keys.isEmpty() )
+        {
+            Map<K, ICacheElement<K, V>> elements = keys.stream()
+                .collect(Collectors.toMap(
+                        key -> key,
+                        key -> get(key))).entrySet().stream()
+                    .filter(entry -> entry.getValue() != null)
+                    .collect(Collectors.toMap(
+                            entry -> entry.getKey(),
+                            entry -> entry.getValue()));
+
+            return elements;
+        }
+
+        return new HashMap<>();
+    }
+
+    /**
+     * Synchronously reads from the lateral cache.
+     * <p>
+     * @param pattern
+     * @return ICacheElement&lt;K, V&gt; if found, else empty
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMatching(String pattern)
+    {
+        getCount++;
+        if ( this.getStatus() != CacheStatus.ERROR )
+        {
+            try
+            {
+                return cache.getMatching( pattern );
+            }
+            catch ( UnmarshalException ue )
+            {
+                log.debug( "Retrying the get owing to UnmarshalException." );
+                try
+                {
+                    return cache.getMatching( pattern );
+                }
+                catch ( IOException ex )
+                {
+                    log.error( "Failed in retrying the get for the second time." );
+                    eventQueue.destroy();
+                }
+            }
+            catch ( IOException ex )
+            {
+                eventQueue.destroy();
+            }
+        }
+        return Collections.emptyMap();
+    }
+
+    /**
+     * Return the keys in this cache.
+     * <p>
+     * @see org.apache.commons.jcs3.auxiliary.AuxiliaryCache#getKeySet()
+     */
+    @Override
+    public Set<K> getKeySet() throws IOException
+    {
+        try
+        {
+            return cache.getKeySet();
+        }
+        catch ( IOException ex )
+        {
+            log.error( ex );
+            eventQueue.destroy();
+        }
+        return Collections.emptySet();
+    }
+
+    /**
+     * Adds a remove request to the lateral cache.
+     * <p>
+     * @param key
+     * @return always false
+     */
+    @Override
+    public boolean remove( K key )
+    {
+        removeCount++;
+        try
+        {
+            eventQueue.addRemoveEvent( key );
+        }
+        catch ( IOException ex )
+        {
+            log.error( ex );
+            eventQueue.destroy();
+        }
+        return false;
+    }
+
+    /** Adds a removeAll request to the lateral cache. */
+    @Override
+    public void removeAll()
+    {
+        try
+        {
+            eventQueue.addRemoveAllEvent();
+        }
+        catch ( IOException ex )
+        {
+            log.error( ex );
+            eventQueue.destroy();
+        }
+    }
+
+    /** Adds a dispose request to the lateral cache. */
+    @Override
+    public void dispose()
+    {
+        try
+        {
+            eventQueue.addDisposeEvent();
+        }
+        catch ( IOException ex )
+        {
+            log.error( ex );
+            eventQueue.destroy();
+        }
+    }
+
+    /**
+     * No lateral invocation.
+     * <p>
+     * @return The size value
+     */
+    @Override
+    public int getSize()
+    {
+        return cache.getSize();
+    }
+
+    /**
+     * No lateral invocation.
+     * <p>
+     * @return The cacheType value
+     */
+    @Override
+    public CacheType getCacheType()
+    {
+        return cache.getCacheType();
+    }
+
+    /**
+     * Returns the asyn cache status. An error status indicates either the lateral connection is not
+     * available, or the asyn queue has been unexpectedly destroyed. No lateral invocation.
+     * <p>
+     * @return The status value
+     */
+    @Override
+    public CacheStatus getStatus()
+    {
+        return eventQueue.isWorking() ? cache.getStatus() : CacheStatus.ERROR;
+    }
+
+    /**
+     * Gets the cacheName attribute of the LateralCacheNoWait object
+     * <p>
+     * @return The cacheName value
+     */
+    @Override
+    public String getCacheName()
+    {
+        return cache.getCacheName();
+    }
+
+    /**
+     * Replaces the lateral cache service handle with the given handle and reset the queue by
+     * starting up a new instance.
+     * <p>
+     * @param lateral
+     */
+    public void fixCache( ICacheServiceNonLocal<K, V> lateral )
+    {
+        cache.fixCache( lateral );
+        resetEventQ();
+    }
+
+    /**
+     * Resets the event q by first destroying the existing one and starting up new one.
+     */
+    public void resetEventQ()
+    {
+        if ( eventQueue.isWorking() )
+        {
+            eventQueue.destroy();
+        }
+        CacheEventQueueFactory<K, V> fact = new CacheEventQueueFactory<>();
+        this.eventQueue = fact.createCacheEventQueue( new CacheAdaptor<>( cache ), CacheInfo.listenerId, cache
+            .getCacheName(), cache.getAuxiliaryCacheAttributes().getEventQueuePoolName(), cache
+            .getAuxiliaryCacheAttributes().getEventQueueType() );
+    }
+
+    /**
+     * @return Returns the AuxiliaryCacheAttributes.
+     */
+    @Override
+    public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
+    {
+        return cache.getAuxiliaryCacheAttributes();
+    }
+
+    /**
+     * getStats
+     * @return String
+     */
+    @Override
+    public String getStats()
+    {
+        return getStatistics().toString();
+    }
+
+    /**
+     * this won't be called since we don't do ICache logging here.
+     * <p>
+     * @return String
+     */
+    @Override
+    public String getEventLoggingExtraInfo()
+    {
+        return "Lateral Cache No Wait";
+    }
+
+    /**
+     * @return statistics about this communication
+     */
+    @Override
+    public IStats getStatistics()
+    {
+        IStats stats = new Stats();
+        stats.setTypeName( "Lateral Cache No Wait" );
+
+        ArrayList<IStatElement<?>> elems = new ArrayList<>();
+
+        // get the stats from the event queue too
+        IStats eqStats = this.eventQueue.getStatistics();
+        elems.addAll(eqStats.getStatElements());
+
+        elems.add(new StatElement<>( "Get Count", Integer.valueOf(this.getCount) ) );
+        elems.add(new StatElement<>( "Remove Count", Integer.valueOf(this.removeCount) ) );
+        elems.add(new StatElement<>( "Put Count", Integer.valueOf(this.putCount) ) );
+        elems.add(new StatElement<>( "Attributes", cache.getAuxiliaryCacheAttributes() ) );
+
+        stats.setStatElements( elems );
+
+        return stats;
+    }
+
+    /**
+     * @return debugging info.
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        buf.append( " LateralCacheNoWait " );
+        buf.append( " Status = " + this.getStatus() );
+        buf.append( " cache = [" + cache.toString() + "]" );
+        return buf.toString();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/LateralCacheNoWaitFacade.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/LateralCacheNoWaitFacade.java
new file mode 100644
index 0000000..18e4c22
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/LateralCacheNoWaitFacade.java
@@ -0,0 +1,467 @@
+package org.apache.commons.jcs3.auxiliary.lateral;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCache;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.lateral.behavior.ILateralCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.lateral.behavior.ILateralCacheListener;
+import org.apache.commons.jcs3.engine.CacheStatus;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.stats.StatElement;
+import org.apache.commons.jcs3.engine.stats.Stats;
+import org.apache.commons.jcs3.engine.stats.behavior.IStatElement;
+import org.apache.commons.jcs3.engine.stats.behavior.IStats;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * Used to provide access to multiple services under nowait protection. Composite factory should
+ * construct LateralCacheNoWaitFacade to give to the composite cache out of caches it constructs
+ * from the varies manager to lateral services. Perhaps the lateralcache factory should be able to
+ * do this.
+ */
+public class LateralCacheNoWaitFacade<K, V>
+    extends AbstractAuxiliaryCache<K, V>
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog( LateralCacheNoWaitFacade.class );
+
+    /** The queuing facade to the client. */
+    public LateralCacheNoWait<K, V>[] noWaits;
+
+    /** The region name */
+    private final String cacheName;
+
+    /** A cache listener */
+    private ILateralCacheListener<K, V> listener;
+
+    /** User configurable attributes. */
+    private final ILateralCacheAttributes lateralCacheAttributes;
+
+    /** Disposed state of this facade */
+    private boolean disposed = false;
+
+    /**
+     * Constructs with the given lateral cache, and fires events to any listeners.
+     * <p>
+     * @param noWaits
+     * @param cattr
+     */
+    public LateralCacheNoWaitFacade(ILateralCacheListener<K, V> listener, LateralCacheNoWait<K, V>[] noWaits, ILateralCacheAttributes cattr )
+    {
+        log.debug( "CONSTRUCTING NO WAIT FACADE" );
+        this.listener = listener;
+        this.noWaits = noWaits;
+        this.cacheName = cattr.getCacheName();
+        this.lateralCacheAttributes = cattr;
+    }
+
+    /**
+     * Tells you if the no wait is in the list or not.
+     * <p>
+     * @param noWait
+     * @return true if the noWait is in the list.
+     */
+    public boolean containsNoWait( LateralCacheNoWait<K, V> noWait )
+    {
+        Optional<LateralCacheNoWait<K, V>> optional = Arrays.stream(noWaits)
+                // we know noWait isn't null
+                .filter(nw -> noWait.equals( nw ))
+                .findFirst();
+
+        return optional.isPresent();
+    }
+
+    /**
+     * Adds a no wait to the list if it isn't already in the list.
+     * <p>
+     * @param noWait
+     * @return true if it wasn't already contained
+     */
+    public synchronized boolean addNoWait( LateralCacheNoWait<K, V> noWait )
+    {
+        if ( noWait == null )
+        {
+            return false;
+        }
+
+        if ( containsNoWait( noWait ) )
+        {
+            log.debug( "No Wait already contained, [{0}]", noWait );
+            return false;
+        }
+
+        @SuppressWarnings("unchecked") // No generic arrays in java
+        LateralCacheNoWait<K, V>[] newArray = new LateralCacheNoWait[noWaits.length + 1];
+
+        System.arraycopy( noWaits, 0, newArray, 0, noWaits.length );
+
+        // set the last position to the new noWait
+        newArray[noWaits.length] = noWait;
+
+        noWaits = newArray;
+
+        return true;
+    }
+
+    /**
+     * Removes a no wait from the list if it is already there.
+     * <p>
+     * @param noWait
+     * @return true if it was already in the array
+     */
+    public synchronized boolean removeNoWait( LateralCacheNoWait<K, V> noWait )
+    {
+        if ( noWait == null )
+        {
+            return false;
+        }
+
+        int position = -1;
+        for ( int i = 0; i < noWaits.length; i++ )
+        {
+            // we know noWait isn't null
+            if ( noWait.equals( noWaits[i] ) )
+            {
+                position = i;
+                break;
+            }
+        }
+
+        if ( position == -1 )
+        {
+            return false;
+        }
+
+        @SuppressWarnings("unchecked") // No generic arrays in java
+        LateralCacheNoWait<K, V>[] newArray = new LateralCacheNoWait[noWaits.length - 1];
+
+        System.arraycopy( noWaits, 0, newArray, 0, position );
+        if ( noWaits.length != position )
+        {
+            System.arraycopy( noWaits, position + 1, newArray, position, noWaits.length - position - 1 );
+        }
+        noWaits = newArray;
+
+        return true;
+    }
+
+    /**
+     * @param ce
+     * @throws IOException
+     */
+    @Override
+    public void update( ICacheElement<K, V> ce )
+        throws IOException
+    {
+        log.debug( "updating through lateral cache facade, noWaits.length = {0}",
+                noWaits.length );
+
+        for (LateralCacheNoWait<K, V> nw : noWaits)
+        {
+            nw.update( ce );
+        }
+    }
+
+    /**
+     * Synchronously reads from the lateral cache.
+     * <p>
+     * @param key
+     * @return ICacheElement
+     */
+    @Override
+    public ICacheElement<K, V> get( K key )
+    {
+        Optional<ICacheElement<K, V>> optional = Arrays.stream(noWaits)
+            .map(nw -> nw.get( key ))
+            .filter(obj -> obj != null)
+            .findFirst();
+
+        if (optional.isPresent())
+        {
+            return optional.get();
+        }
+
+        return null;
+    }
+
+    /**
+     * Gets multiple items from the cache based on the given set of keys.
+     * <p>
+     * @param keys
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache for any of these keys
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMultiple(Set<K> keys)
+    {
+        if ( keys != null && !keys.isEmpty() )
+        {
+            Map<K, ICacheElement<K, V>> elements = keys.stream()
+                .collect(Collectors.toMap(
+                        key -> key,
+                        key -> get(key))).entrySet().stream()
+                    .filter(entry -> entry.getValue() != null)
+                    .collect(Collectors.toMap(
+                            entry -> entry.getKey(),
+                            entry -> entry.getValue()));
+
+            return elements;
+        }
+
+        return new HashMap<>();
+    }
+
+    /**
+     * Synchronously reads from the lateral cache. Get a response from each! This will be slow.
+     * Merge them.
+     * <p>
+     * @param pattern
+     * @return ICacheElement
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMatching(String pattern)
+    {
+        Map<K, ICacheElement<K, V>> elements = new HashMap<>();
+        for (LateralCacheNoWait<K, V> nw : noWaits)
+        {
+            elements.putAll( nw.getMatching( pattern ) );
+        }
+        return elements;
+    }
+
+    /**
+     * Return the keys in this cache.
+     * <p>
+     * @see org.apache.commons.jcs3.auxiliary.AuxiliaryCache#getKeySet()
+     */
+    @Override
+    public Set<K> getKeySet() throws IOException
+    {
+        HashSet<K> allKeys = new HashSet<>();
+        for (LateralCacheNoWait<K, V> nw : noWaits)
+        {
+            if ( nw != null )
+            {
+                Set<K> keys = nw.getKeySet();
+                if (keys != null)
+                {
+                    allKeys.addAll( keys );
+                }
+            }
+        }
+        return allKeys;
+    }
+
+    /**
+     * Adds a remove request to the lateral cache.
+     * <p>
+     * @param key
+     * @return always false.
+     */
+    @Override
+    public boolean remove( K key )
+    {
+        Arrays.stream(noWaits).forEach(nw -> nw.remove( key ));
+        return false;
+    }
+
+    /**
+     * Adds a removeAll request to the lateral cache.
+     */
+    @Override
+    public void removeAll()
+    {
+        Arrays.stream(noWaits).forEach(nw -> nw.removeAll());
+    }
+
+    /** Adds a dispose request to the lateral cache. */
+    @Override
+    public void dispose()
+    {
+        try
+        {
+            if ( listener != null )
+            {
+                listener.dispose();
+                listener = null;
+            }
+
+            Arrays.stream(noWaits).forEach(nw -> nw.dispose());
+        }
+        finally
+        {
+            disposed = true;
+        }
+    }
+
+    /**
+     * No lateral invocation.
+     * @return The size value
+     */
+    @Override
+    public int getSize()
+    {
+        return 0;
+        //cache.getSize();
+    }
+
+    /**
+     * Gets the cacheType attribute of the LateralCacheNoWaitFacade object.
+     * <p>
+     * @return The cacheType value
+     */
+    @Override
+    public CacheType getCacheType()
+    {
+        return CacheType.LATERAL_CACHE;
+    }
+
+    /**
+     * Gets the cacheName attribute of the LateralCacheNoWaitFacade object.
+     * <p>
+     * @return The cacheName value
+     */
+    @Override
+    public String getCacheName()
+    {
+        return "";
+        //cache.getCacheName();
+    }
+
+    /**
+     * Gets the status attribute of the LateralCacheNoWaitFacade object
+     * @return The status value
+     */
+    @Override
+    public CacheStatus getStatus()
+    {
+        if (disposed)
+        {
+            return CacheStatus.DISPOSED;
+        }
+
+        if (noWaits.length == 0 || listener != null)
+        {
+            return CacheStatus.ALIVE;
+        }
+
+        List<CacheStatus> statii = Arrays.stream(noWaits)
+                .map(nw -> nw.getStatus())
+                .collect(Collectors.toList());
+
+        // It's alive if ANY of its nowaits is alive
+        if (statii.contains(CacheStatus.ALIVE))
+        {
+            return CacheStatus.ALIVE;
+        }
+        // It's alive if ANY of its nowaits is in error, but
+        // none are alive, then it's in error
+        if (statii.contains(CacheStatus.ERROR))
+        {
+            return CacheStatus.ERROR;
+        }
+
+        // Otherwise, it's been disposed, since it's the only status left
+        return CacheStatus.DISPOSED;
+    }
+
+    /**
+     * @return Returns the AuxiliaryCacheAttributes.
+     */
+    @Override
+    public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
+    {
+        return this.lateralCacheAttributes;
+    }
+
+    /**
+     * @return "LateralCacheNoWaitFacade: " + cacheName;
+     */
+    @Override
+    public String toString()
+    {
+        return "LateralCacheNoWaitFacade: " + cacheName;
+    }
+
+    /**
+     * this won't be called since we don't do ICache logging here.
+     * <p>
+     * @return String
+     */
+    @Override
+    public String getEventLoggingExtraInfo()
+    {
+        return "Lateral Cache No Wait";
+    }
+
+    /**
+     * getStats
+     * @return String
+     */
+    @Override
+    public String getStats()
+    {
+        return getStatistics().toString();
+    }
+
+    /**
+     * @return IStats
+     */
+    @Override
+    public IStats getStatistics()
+    {
+        IStats stats = new Stats();
+        stats.setTypeName( "Lateral Cache No Wait Facade" );
+
+        ArrayList<IStatElement<?>> elems = new ArrayList<>();
+
+        if ( noWaits != null )
+        {
+            elems.add(new StatElement<>( "Number of No Waits", Integer.valueOf(noWaits.length) ) );
+
+            for ( LateralCacheNoWait<K, V> lcnw : noWaits )
+            {
+                if ( lcnw != null )
+                {
+                    // get the stats from the super too
+                    IStats sStats = lcnw.getStatistics();
+                    elems.addAll(sStats.getStatElements());
+                }
+            }
+        }
+
+        stats.setStatElements( elems );
+
+        return stats;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/LateralCommand.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/LateralCommand.java
new file mode 100644
index 0000000..410f023
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/LateralCommand.java
@@ -0,0 +1,47 @@
+package org.apache.commons.jcs3.auxiliary.lateral;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Enumeration of the available lateral commands
+ */
+public enum LateralCommand
+{
+    /** The command for updates */
+    UPDATE,
+
+    /** The command for removes */
+    REMOVE,
+
+    /** The command instructing us to remove all */
+    REMOVEALL,
+
+    /** The command for disposing the cache. */
+    DISPOSE,
+
+    /** Command to return an object. */
+    GET,
+
+    /** Command to return an object. */
+    GET_MATCHING,
+
+    /** Command to get all keys */
+    GET_KEYSET
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/LateralElementDescriptor.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/LateralElementDescriptor.java
new file mode 100644
index 0000000..f2d3b82
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/LateralElementDescriptor.java
@@ -0,0 +1,83 @@
+package org.apache.commons.jcs3.auxiliary.lateral;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.Serializable;
+
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+
+/**
+ * This class wraps command to other laterals. It is essentially a
+ * JCS-TCP-Lateral packet. The headers specify the action the receiver should
+ * take.
+ */
+public class LateralElementDescriptor<K, V>
+    implements Serializable
+{
+    /** Don't change */
+    private static final long serialVersionUID = 5268222498076063575L;
+
+    /** The Cache Element that we are distributing. */
+    public ICacheElement<K, V> ce;
+
+    /**
+     * The id of the the source of the request. This is used to prevent infinite
+     * loops.
+     */
+    public long requesterId;
+
+    /** The operation has been requested by the client. */
+    public LateralCommand command = LateralCommand.UPDATE;
+
+    /**
+     * The hashcode value for this element.
+     */
+    public int valHashCode = -1;
+
+    /** Constructor for the LateralElementDescriptor object */
+    public LateralElementDescriptor()
+    {
+        super();
+    }
+
+    /**
+     * Constructor for the LateralElementDescriptor object
+     * <p>
+     * @param ce ICacheElement&lt;K, V&gt; payload
+     */
+    public LateralElementDescriptor( ICacheElement<K, V> ce )
+    {
+        this.ce = ce;
+    }
+
+    /**
+     * @return String, all the important values that can be configured
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        buf.append( "\n LateralElementDescriptor " );
+        buf.append( "\n command = [" + this.command + "]" );
+        buf.append( "\n valHashCode = [" + this.valHashCode + "]" );
+        buf.append( "\n ICacheElement = [" + this.ce + "]" );
+        return buf.toString();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/behavior/ILateralCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/behavior/ILateralCacheAttributes.java
new file mode 100644
index 0000000..6cec965
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/behavior/ILateralCacheAttributes.java
@@ -0,0 +1,200 @@
+package org.apache.commons.jcs3.auxiliary.lateral.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
+
+/**
+ * This interface defines configuration options common to lateral cache plugins.
+ * <p>
+ * TODO it needs to be trimmed down. The old version had features for every lateral. Now, the
+ * individual laterals have their own specific attributes interfaces.
+ */
+public interface ILateralCacheAttributes
+    extends AuxiliaryCacheAttributes
+{
+    enum Type
+    {
+        /** HTTP type */
+        HTTP, // 1
+
+        /** UDP type */
+        UDP, // 2
+
+        /** TCP type */
+        TCP, // 3
+
+        /** XMLRPC type */
+        XMLRPC // 4
+    }
+
+    /**
+     * The number of elements the zombie queue will hold. This queue is used to store events if we
+     * loose our connection with the server.
+     */
+    int DEFAULT_ZOMBIE_QUEUE_MAX_SIZE = 1000;
+
+    /**
+     * Sets the httpServer attribute of the ILateralCacheAttributes object
+     * <p>
+     * @param val The new httpServer value
+     */
+    void setHttpServer( String val );
+
+    /**
+     * Gets the httpServer attribute of the ILateralCacheAttributes object
+     * <p>
+     * @return The httpServer value
+     */
+    String getHttpServer();
+
+    /**
+     * Sets the httpListenerPort attribute of the ILateralCacheAttributes object
+     * <p>
+     * @param val The new tcpListenerPort value
+     */
+    void setHttpListenerPort( int val );
+
+    /**
+     * Gets the httpListenerPort attribute of the ILateralCacheAttributes object
+     * <p>
+     * @return The httpListenerPort value
+     */
+    int getHttpListenerPort();
+
+    /**
+     * Sets the httpServers attribute of the LateralCacheAttributes object
+     * <p>
+     * @param val The new httpServers value
+     */
+    void setHttpServers( String val );
+
+    /**
+     * Gets the httpSrvers attribute of the LateralCacheAttributes object
+     * <p>
+     * @return The httpServers value
+     */
+    String getHttpServers();
+
+    /**
+     * Sets the udpMulticastAddr attribute of the ILateralCacheAttributes object
+     * <p>
+     * @param val The new udpMulticastAddr value
+     */
+    void setUdpMulticastAddr( String val );
+
+    /**
+     * Gets the udpMulticastAddr attribute of the ILateralCacheAttributes object
+     * <p>
+     * @return The udpMulticastAddr value
+     */
+    String getUdpMulticastAddr();
+
+    /**
+     * Sets the udpMulticastPort attribute of the ILateralCacheAttributes object
+     * <p>
+     * @param val The new udpMulticastPort value
+     */
+    void setUdpMulticastPort( int val );
+
+    /**
+     * Gets the udpMulticastPort attribute of the ILateralCacheAttributes object
+     * <p>
+     * @return The udpMulticastPort value
+     */
+    int getUdpMulticastPort();
+
+    /**
+     * Sets the transmissionType attribute of the ILateralCacheAttributes object
+     * <p>
+     * @param val The new transmissionType value
+     */
+    void setTransmissionType( Type val );
+
+    /**
+     * Gets the transmissionType attribute of the ILateralCacheAttributes object
+     * <p>
+     * @return The transmissionType value
+     */
+    Type getTransmissionType();
+
+    /**
+     * Sets the transmissionTypeName attribute of the ILateralCacheAttributes object
+     * <p>
+     * @param val The new transmissionTypeName value
+     */
+    void setTransmissionTypeName( String val );
+
+    /**
+     * Gets the transmissionTypeName attribute of the ILateralCacheAttributes object
+     * <p>
+     * @return The transmissionTypeName value
+     */
+    String getTransmissionTypeName();
+
+    /**
+     * Sets the putOnlyMode attribute of the ILateralCacheAttributes. When this is true the lateral
+     * cache will only issue put and remove order and will not try to retrieve elements from other
+     * lateral caches.
+     * <p>
+     * @param val The new transmissionTypeName value
+     */
+    void setPutOnlyMode( boolean val );
+
+    /**
+     * @return The outgoingOnlyMode value. Stops gets from going remote.
+     */
+    boolean getPutOnlyMode();
+
+    /**
+     * @param receive The receive to set.
+     */
+    void setReceive( boolean receive );
+
+    /**
+     * Should a listener be created. By default this is true.
+     * <p>
+     * If this is false the lateral will connect to others but it will not create a listener to
+     * receive.
+     * <p>
+     * It is possible if two laterals are misconfigured that lateral A may have a region R1 that is
+     * not configured for the lateral but another is. And if cache B has region R1 configured for
+     * lateral distribution, A will get messages for R1 but not send them.
+     * <p>
+     * @return true if we should have a listener connection
+     */
+    boolean isReceive();
+
+    /**
+     * The number of elements the zombie queue will hold. This queue is used to store events if we
+     * loose our connection with the server.
+     * <p>
+     * @param zombieQueueMaxSize The zombieQueueMaxSize to set.
+     */
+    void setZombieQueueMaxSize( int zombieQueueMaxSize );
+
+    /**
+     * The number of elements the zombie queue will hold. This queue is used to store events if we
+     * loose our connection with the server.
+     * <p>
+     * @return Returns the zombieQueueMaxSize.
+     */
+    int getZombieQueueMaxSize();
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/behavior/ILateralCacheListener.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/behavior/ILateralCacheListener.java
new file mode 100644
index 0000000..006a3f5
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/behavior/ILateralCacheListener.java
@@ -0,0 +1,51 @@
+package org.apache.commons.jcs3.auxiliary.lateral.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.engine.behavior.ICacheListener;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
+
+/**
+ * Listens for lateral cache event notification.
+ */
+public interface ILateralCacheListener<K, V>
+    extends ICacheListener<K, V>
+{
+    /**
+     * Initialize this listener
+     */
+    void init();
+
+    /**
+     * @param cacheMgr
+     *            The cacheMgr to set.
+     */
+    void setCacheManager( ICompositeCacheManager cacheMgr );
+
+    /**
+     * @return Returns the cacheMgr.
+     */
+    ICompositeCacheManager getCacheManager();
+
+    /**
+     * Dispose this listener
+     */
+    void dispose();
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/package.html b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/package.html
similarity index 100%
rename from commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/lateral/package.html
rename to commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/package.html
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPCacheFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPCacheFactory.java
new file mode 100644
index 0000000..ce7d98d
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPCacheFactory.java
@@ -0,0 +1,404 @@
+package org.apache.commons.jcs3.auxiliary.lateral.socket.tcp;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.StringTokenizer;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCacheFactory;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.lateral.LateralCache;
+import org.apache.commons.jcs3.auxiliary.lateral.LateralCacheMonitor;
+import org.apache.commons.jcs3.auxiliary.lateral.LateralCacheNoWait;
+import org.apache.commons.jcs3.auxiliary.lateral.LateralCacheNoWaitFacade;
+import org.apache.commons.jcs3.auxiliary.lateral.behavior.ILateralCacheListener;
+import org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.behavior.ITCPLateralCacheAttributes;
+import org.apache.commons.jcs3.engine.CacheWatchRepairable;
+import org.apache.commons.jcs3.engine.ZombieCacheServiceNonLocal;
+import org.apache.commons.jcs3.engine.ZombieCacheWatch;
+import org.apache.commons.jcs3.engine.behavior.ICache;
+import org.apache.commons.jcs3.engine.behavior.ICacheServiceNonLocal;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.engine.behavior.IShutdownObserver;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.discovery.UDPDiscoveryManager;
+import org.apache.commons.jcs3.utils.discovery.UDPDiscoveryService;
+
+/**
+ * Constructs a LateralCacheNoWaitFacade for the given configuration. Each lateral service / local
+ * relationship is managed by one manager. This manager can have multiple caches. The remote
+ * relationships are consolidated and restored via these managers.
+ * <p>
+ * The facade provides a front to the composite cache so the implementation is transparent.
+ */
+public class LateralTCPCacheFactory
+    extends AbstractAuxiliaryCacheFactory
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog( LateralTCPCacheFactory.class );
+
+    /** Address to service map. */
+    private ConcurrentHashMap<String, ICacheServiceNonLocal<?, ?>> csnlInstances;
+
+    /** Map of available discovery listener instances, keyed by port. */
+    private ConcurrentHashMap<String, LateralTCPDiscoveryListener> lTCPDLInstances;
+
+    /** Monitor thread */
+    private LateralCacheMonitor monitor;
+
+    /**
+     * Wrapper of the lateral cache watch service; or wrapper of a zombie
+     * service if failed to connect.
+     */
+    private CacheWatchRepairable lateralWatch;
+
+    /**
+     * Creates a TCP lateral.
+     * <p>
+     * @param iaca
+     * @param cacheMgr
+     * @param cacheEventLogger
+     * @param elementSerializer
+     * @return LateralCacheNoWaitFacade
+     */
+    @Override
+    public <K, V> LateralCacheNoWaitFacade<K, V> createCache(
+            AuxiliaryCacheAttributes iaca, ICompositeCacheManager cacheMgr,
+           ICacheEventLogger cacheEventLogger, IElementSerializer elementSerializer )
+    {
+        ITCPLateralCacheAttributes lac = (ITCPLateralCacheAttributes) iaca;
+        ArrayList<ICache<K, V>> noWaits = new ArrayList<>();
+
+        // pairs up the tcp servers and set the tcpServer value and
+        // get the manager and then get the cache
+        // no servers are required.
+        if ( lac.getTcpServers() != null )
+        {
+            StringTokenizer it = new StringTokenizer( lac.getTcpServers(), "," );
+            log.debug( "Configured for [{0}] servers.", () -> it.countTokens() );
+
+            while ( it.hasMoreElements() )
+            {
+                String server = (String) it.nextElement();
+                log.debug( "tcp server = {0}", server );
+                ITCPLateralCacheAttributes lacC = (ITCPLateralCacheAttributes) lac.clone();
+                lacC.setTcpServer( server );
+
+                LateralCacheNoWait<K, V> lateralNoWait = createCacheNoWait(lacC, cacheEventLogger, elementSerializer);
+
+                addListenerIfNeeded( lacC, cacheMgr );
+                monitor.addCache(lateralNoWait);
+                noWaits.add( lateralNoWait );
+            }
+        }
+
+        ILateralCacheListener<K, V> listener = createListener( lac, cacheMgr );
+
+        // create the no wait facade.
+        @SuppressWarnings("unchecked") // No generic arrays in java
+        LateralCacheNoWait<K, V>[] lcnwArray = noWaits.toArray( new LateralCacheNoWait[0] );
+        LateralCacheNoWaitFacade<K, V> lcnwf =
+            new LateralCacheNoWaitFacade<>(listener, lcnwArray, lac );
+
+        // create udp discovery if available.
+        createDiscoveryService( lac, lcnwf, cacheMgr, cacheEventLogger, elementSerializer );
+
+        return lcnwf;
+    }
+
+    protected <K, V> LateralCacheNoWait<K, V> createCacheNoWait( ITCPLateralCacheAttributes lca,
+            ICacheEventLogger cacheEventLogger, IElementSerializer elementSerializer )
+    {
+        ICacheServiceNonLocal<K, V> lateralService = getCSNLInstance(lca);
+
+        LateralCache<K, V> cache = new LateralCache<>( lca, lateralService, this.monitor );
+        cache.setCacheEventLogger( cacheEventLogger );
+        cache.setElementSerializer( elementSerializer );
+
+        log.debug( "Created cache for noWait, cache [{0}]", cache );
+
+        LateralCacheNoWait<K, V> lateralNoWait = new LateralCacheNoWait<>( cache );
+        lateralNoWait.setCacheEventLogger( cacheEventLogger );
+        lateralNoWait.setElementSerializer( elementSerializer );
+
+        log.info( "Created LateralCacheNoWait for [{0}] LateralCacheNoWait = [{1}]",
+                lca, lateralNoWait );
+
+        return lateralNoWait;
+    }
+
+    /**
+     * Initialize this factory
+     */
+    @Override
+    public void initialize()
+    {
+        this.csnlInstances = new ConcurrentHashMap<>();
+        this.lTCPDLInstances = new ConcurrentHashMap<>();
+
+        // Create the monitoring daemon thread
+        this.monitor = new LateralCacheMonitor(this);
+        this.monitor.setDaemon( true );
+        this.monitor.start();
+
+        this.lateralWatch = new CacheWatchRepairable();
+        this.lateralWatch.setCacheWatch( new ZombieCacheWatch() );
+    }
+
+    /**
+     * Dispose of this factory, clean up shared resources
+     */
+    @Override
+    public void dispose()
+    {
+        for (ICacheServiceNonLocal<?, ?> service : this.csnlInstances.values())
+        {
+            try
+            {
+                service.dispose("");
+            }
+            catch (IOException e)
+            {
+                log.error("Could not dispose service " + service, e);
+            }
+        }
+
+        this.csnlInstances.clear();
+
+        // TODO: shut down discovery listeners
+        this.lTCPDLInstances.clear();
+
+        if (this.monitor != null)
+        {
+            this.monitor.notifyShutdown();
+            try
+            {
+                this.monitor.join(5000);
+            }
+            catch (InterruptedException e)
+            {
+                // swallow
+            }
+            this.monitor = null;
+        }
+    }
+
+    /**
+     * Returns an instance of the cache service.
+     * <p>
+     * @param lca configuration for the creation of a new service instance
+     *
+     * @return ICacheServiceNonLocal&lt;K, V&gt;
+     */
+    // Need to cast because of common map for all cache services
+    @SuppressWarnings("unchecked")
+    public <K, V> ICacheServiceNonLocal<K, V> getCSNLInstance( ITCPLateralCacheAttributes lca )
+    {
+        String key = lca.getTcpServer();
+
+        csnlInstances.computeIfPresent(key, (name, service) -> {
+            // If service creation did not succeed last time, force retry
+            if (service instanceof ZombieCacheServiceNonLocal)
+            {
+                log.info("Disposing of zombie service instance for [{0}]", name);
+                return null;
+            }
+
+            return service;
+        });
+
+        ICacheServiceNonLocal<K, V> service =
+                (ICacheServiceNonLocal<K, V>) csnlInstances.computeIfAbsent(key, name -> {
+
+                    log.info( "Instance for [{0}] is null, creating", name );
+
+                    // Create the service
+                    try
+                    {
+                        log.info( "Creating TCP service, lca = {0}", lca );
+
+                        return new LateralTCPService<>( lca );
+                    }
+                    catch ( IOException ex )
+                    {
+                        // Failed to connect to the lateral server.
+                        // Configure this LateralCacheManager instance to use the
+                        // "zombie" services.
+                        log.error( "Failure, lateral instance will use zombie service", ex );
+
+                        ICacheServiceNonLocal<K, V> zombieService =
+                                new ZombieCacheServiceNonLocal<>( lca.getZombieQueueMaxSize() );
+
+                        // Notify the cache monitor about the error, and kick off
+                        // the recovery process.
+                        monitor.notifyError();
+
+                        return zombieService;
+                    }
+                });
+
+        return service;
+    }
+
+    /**
+     * Gets the instance attribute of the LateralCacheTCPListener class.
+     * <p>
+     * @param ilca ITCPLateralCacheAttributes
+     * @param cacheManager a reference to the global cache manager
+     *
+     * @return The instance value
+     */
+    private LateralTCPDiscoveryListener getDiscoveryListener(ITCPLateralCacheAttributes ilca, ICompositeCacheManager cacheManager)
+    {
+        String key = ilca.getUdpDiscoveryAddr() + ":" + ilca.getUdpDiscoveryPort();
+
+        LateralTCPDiscoveryListener ins = lTCPDLInstances.computeIfAbsent(key, key1 -> {
+            log.info("Created new discovery listener for cacheName {0} for request {1}",
+                    key1, ilca.getCacheName());
+            return new LateralTCPDiscoveryListener( this.getName(),  cacheManager);
+        });
+
+        return ins;
+    }
+
+    /**
+     * Add listener for receivers
+     * <p>
+     * @param iaca cache configuration attributes
+     * @param cacheMgr the composite cache manager
+     */
+    private void addListenerIfNeeded( ITCPLateralCacheAttributes iaca, ICompositeCacheManager cacheMgr )
+    {
+        // don't create a listener if we are not receiving.
+        if ( iaca.isReceive() )
+        {
+            try
+            {
+                addLateralCacheListener( iaca.getCacheName(),
+                        LateralTCPListener.getInstance( iaca, cacheMgr ) );
+            }
+            catch ( IOException ioe )
+            {
+                log.error("Problem creating lateral listener", ioe);
+            }
+        }
+        else
+        {
+            log.debug( "Not creating a listener since we are not receiving." );
+        }
+    }
+
+    /**
+     * Adds the lateral cache listener to the underlying cache-watch service.
+     * <p>
+     * @param cacheName The feature to be added to the LateralCacheListener attribute
+     * @param listener The feature to be added to the LateralCacheListener attribute
+     * @throws IOException
+     */
+    private <K, V> void addLateralCacheListener( String cacheName, ILateralCacheListener<K, V> listener )
+        throws IOException
+    {
+        synchronized ( this.lateralWatch )
+        {
+            lateralWatch.addCacheListener( cacheName, listener );
+        }
+    }
+
+    /**
+     * Makes sure a listener gets created. It will get monitored as soon as it
+     * is used.
+     * <p>
+     * This should be called by create cache.
+     * <p>
+     * @param attr  ITCPLateralCacheAttributes
+     * @param cacheMgr
+     *
+     * @return the listener if created, else null
+     */
+    private <K, V> ILateralCacheListener<K, V> createListener( ITCPLateralCacheAttributes attr,
+            ICompositeCacheManager cacheMgr )
+    {
+        ILateralCacheListener<K, V> listener = null;
+
+        // don't create a listener if we are not receiving.
+        if ( attr.isReceive() )
+        {
+            log.info( "Getting listener for {0}", attr );
+
+            // make a listener. if one doesn't exist
+            listener = LateralTCPListener.getInstance( attr, cacheMgr );
+
+            // register for shutdown notification
+            cacheMgr.registerShutdownObserver( (IShutdownObserver) listener );
+        }
+        else
+        {
+            log.debug( "Not creating a listener since we are not receiving." );
+        }
+
+        return listener;
+    }
+
+    /**
+     * Creates the discovery service. Only creates this for tcp laterals right now.
+     * <p>
+     * @param lac ITCPLateralCacheAttributes
+     * @param lcnwf
+     * @param cacheMgr
+     * @param cacheEventLogger
+     * @param elementSerializer
+     * @return null if none is created.
+     */
+    private synchronized <K, V> UDPDiscoveryService createDiscoveryService(
+            ITCPLateralCacheAttributes lac,
+            LateralCacheNoWaitFacade<K, V> lcnwf,
+            ICompositeCacheManager cacheMgr,
+            ICacheEventLogger cacheEventLogger,
+            IElementSerializer elementSerializer )
+    {
+        UDPDiscoveryService discovery = null;
+
+        // create the UDP discovery for the TCP lateral
+        if ( lac.isUdpDiscoveryEnabled() )
+        {
+            // One can be used for all regions
+            LateralTCPDiscoveryListener discoveryListener = getDiscoveryListener( lac, cacheMgr );
+            discoveryListener.addNoWaitFacade( lac.getCacheName(), lcnwf );
+
+            // need a factory for this so it doesn't
+            // get dereferenced, also we don't want one for every region.
+            discovery = UDPDiscoveryManager.getInstance().getService( lac.getUdpDiscoveryAddr(),
+                                                                      lac.getUdpDiscoveryPort(),
+                                                                      lac.getTcpListenerPort(), cacheMgr);
+
+            discovery.addParticipatingCacheName( lac.getCacheName() );
+            discovery.addDiscoveryListener( discoveryListener );
+
+            log.info( "Registered TCP lateral cache [{0}] with UDPDiscoveryService.",
+                    () -> lac.getCacheName() );
+        }
+        return discovery;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPDiscoveryListener.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPDiscoveryListener.java
new file mode 100644
index 0000000..a1f953f
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPDiscoveryListener.java
@@ -0,0 +1,315 @@
+package org.apache.commons.jcs3.auxiliary.lateral.socket.tcp;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCache;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.lateral.LateralCacheNoWait;
+import org.apache.commons.jcs3.auxiliary.lateral.LateralCacheNoWaitFacade;
+import org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.behavior.ITCPLateralCacheAttributes;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.discovery.DiscoveredService;
+import org.apache.commons.jcs3.utils.discovery.behavior.IDiscoveryListener;
+
+/**
+ * This knows how to add and remove discovered services. It observes UDP discovery events.
+ * <p>
+ * We can have one listener per region, or one shared by all regions.
+ */
+public class LateralTCPDiscoveryListener
+    implements IDiscoveryListener
+{
+    /** The log factory */
+    private static final Log log = LogManager.getLog( LateralTCPDiscoveryListener.class );
+
+    /**
+     * Map of no wait facades. these are used to determine which regions are locally configured to
+     * use laterals.
+     */
+    private final ConcurrentMap<String, LateralCacheNoWaitFacade<?, ?>> facades =
+        new ConcurrentHashMap<>();
+
+    /**
+     * List of regions that are configured differently here than on another server. We keep track of
+     * this to limit the amount of info logging.
+     */
+    private final CopyOnWriteArrayList<String> knownDifferentlyConfiguredRegions =
+        new CopyOnWriteArrayList<>();
+
+    /** The name of the cache factory */
+    private final String factoryName;
+
+    /** Reference to the cache manager for auxiliary cache access */
+    private final ICompositeCacheManager cacheManager;
+
+    /**
+     * This plugs into the udp discovery system. It will receive add and remove events.
+     * <p>
+     * @param factoryName the name of the related cache factory
+     * @param cacheManager the global cache manager
+     */
+    protected LateralTCPDiscoveryListener( String factoryName, ICompositeCacheManager cacheManager )
+    {
+        this.factoryName = factoryName;
+        this.cacheManager = cacheManager;
+    }
+
+    /**
+     * Adds a nowait facade under this cachename. If one already existed, it will be overridden.
+     * <p>
+     * This adds nowaits to a facade for the region name. If the region has no facade, then it is
+     * not configured to use the lateral cache, and no facade will be created.
+     * <p>
+     * @param cacheName - the region name
+     * @param facade - facade (for region) =&gt; multiple lateral clients.
+     * @return true if the facade was not already registered.
+     */
+    public boolean addNoWaitFacade( String cacheName, LateralCacheNoWaitFacade<?, ?> facade )
+    {
+        boolean isNew = !containsNoWaitFacade( cacheName );
+
+        // override or put anew, it doesn't matter
+        facades.put( cacheName, facade );
+        knownDifferentlyConfiguredRegions.remove( cacheName );
+
+        return isNew;
+    }
+
+    /**
+     * Allows us to see if the facade is present.
+     * <p>
+     * @param cacheName - facades are for a region
+     * @return do we contain the no wait. true if so
+     */
+    public boolean containsNoWaitFacade( String cacheName )
+    {
+        return facades.containsKey( cacheName );
+    }
+
+    /**
+     * Allows us to see if the facade is present and if it has the no wait.
+     * <p>
+     * @param cacheName - facades are for a region
+     * @param noWait - is this no wait in the facade
+     * @return do we contain the no wait. true if so
+     */
+    public <K, V> boolean containsNoWait( String cacheName, LateralCacheNoWait<K, V> noWait )
+    {
+        @SuppressWarnings("unchecked") // Need to cast because of common map for all facades
+        LateralCacheNoWaitFacade<K, V> facade =
+            (LateralCacheNoWaitFacade<K, V>)facades.get( noWait.getCacheName() );
+
+        if ( facade == null )
+        {
+            return false;
+        }
+
+        return facade.containsNoWait( noWait );
+    }
+
+    /**
+     * When a broadcast is received from the UDP Discovery receiver, for each cacheName in the
+     * message, the add no wait will be called here. To add a no wait, the facade is looked up for
+     * this cache name.
+     * <p>
+     * Each region has a facade. The facade contains a list of end points--the other tcp lateral
+     * services.
+     * <p>
+     * @param noWait
+     * @return true if we found the no wait and added it. False if the no wait was not present or if
+     *         we already had it.
+     */
+    protected <K, V> boolean addNoWait( LateralCacheNoWait<K, V> noWait )
+    {
+        @SuppressWarnings("unchecked") // Need to cast because of common map for all facades
+        LateralCacheNoWaitFacade<K, V> facade =
+            (LateralCacheNoWaitFacade<K, V>)facades.get( noWait.getCacheName() );
+        log.debug( "addNoWait > Got facade for {0} = {1}", noWait.getCacheName(), facade );
+
+        if ( facade != null )
+        {
+            boolean isNew = facade.addNoWait( noWait );
+            log.debug( "Called addNoWait, isNew = {0}", isNew );
+            return isNew;
+        }
+        else
+        {
+            if ( knownDifferentlyConfiguredRegions.addIfAbsent( noWait.getCacheName() ) )
+            {
+                log.info( "addNoWait > Different nodes are configured differently "
+                        + "or region [{0}] is not yet used on this side.",
+                        () -> noWait.getCacheName() );
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Look up the facade for the name. If it doesn't exist, then the region is not configured for
+     * use with the lateral cache. If it is present, remove the item from the no wait list.
+     * <p>
+     * @param noWait
+     * @return true if we found the no wait and removed it. False if the no wait was not present.
+     */
+    protected <K, V> boolean removeNoWait( LateralCacheNoWait<K, V> noWait )
+    {
+        @SuppressWarnings("unchecked") // Need to cast because of common map for all facades
+        LateralCacheNoWaitFacade<K, V> facade =
+            (LateralCacheNoWaitFacade<K, V>)facades.get( noWait.getCacheName() );
+        log.debug( "removeNoWait > Got facade for {0} = {1}", noWait.getCacheName(), facade);
+
+        if ( facade != null )
+        {
+            boolean removed = facade.removeNoWait( noWait );
+            log.debug( "Called removeNoWait, removed {0}", removed );
+            return removed;
+        }
+        else
+        {
+            if ( knownDifferentlyConfiguredRegions.addIfAbsent( noWait.getCacheName() ) )
+            {
+                log.info( "addNoWait > Different nodes are configured differently "
+                        + "or region [{0}] is not yet used on this side.",
+                        () -> noWait.getCacheName() );
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Creates the lateral cache if needed.
+     * <p>
+     * We could go to the composite cache manager and get the the cache for the region. This would
+     * force a full configuration of the region. One advantage of this would be that the creation of
+     * the later would go through the factory, which would add the item to the no wait list. But we
+     * don't want to do this. This would force this client to have all the regions as the other.
+     * This might not be desired. We don't want to send or receive for a region here that is either
+     * not used or not configured to use the lateral.
+     * <p>
+     * Right now, I'm afraid that the region will get puts if another instance has the region
+     * configured to use the lateral and our address is configured. This might be a bug, but it
+     * shouldn't happen with discovery.
+     * <p>
+     * @param service
+     */
+    @Override
+    public void addDiscoveredService( DiscoveredService service )
+    {
+        // get a cache and add it to the no waits
+        // the add method should not add the same.
+        // we need the listener port from the original config.
+        ArrayList<String> regions = service.getCacheNames();
+        String serverAndPort = service.getServiceAddress() + ":" + service.getServicePort();
+
+        if ( regions != null )
+        {
+            // for each region get the cache
+            for (String cacheName : regions)
+            {
+                AuxiliaryCache<?, ?> ic = cacheManager.getAuxiliaryCache(factoryName, cacheName);
+
+                log.debug( "Got cache, ic = {0}", ic );
+
+                // add this to the nowaits for this cachename
+                if ( ic != null )
+                {
+                    AuxiliaryCacheAttributes aca = ic.getAuxiliaryCacheAttributes();
+                    if (aca instanceof ITCPLateralCacheAttributes)
+                    {
+                        ITCPLateralCacheAttributes lca = (ITCPLateralCacheAttributes)aca;
+                        if (lca.getTransmissionType() != LateralCacheAttributes.Type.TCP
+                            || !serverAndPort.equals(lca.getTcpServer()) )
+                        {
+                            // skip caches not belonging to this service
+                            continue;
+                        }
+                    }
+
+                    addNoWait( (LateralCacheNoWait<?, ?>) ic );
+                    log.debug( "Called addNoWait for cacheName [{0}]", cacheName );
+                }
+            }
+        }
+        else
+        {
+            log.warn( "No cache names found in message {0}", service );
+        }
+    }
+
+    /**
+     * Removes the lateral cache.
+     * <p>
+     * We need to tell the manager that this instance is bad, so it will reconnect the sender if it
+     * comes back.
+     * <p>
+     * @param service
+     */
+    @Override
+    public void removeDiscoveredService( DiscoveredService service )
+    {
+        // get a cache and add it to the no waits
+        // the add method should not add the same.
+        // we need the listener port from the original config.
+        ArrayList<String> regions = service.getCacheNames();
+        String serverAndPort = service.getServiceAddress() + ":" + service.getServicePort();
+
+        if ( regions != null )
+        {
+            // for each region get the cache
+            for (String cacheName : regions)
+            {
+                AuxiliaryCache<?, ?> ic = cacheManager.getAuxiliaryCache(factoryName, cacheName);
+
+                log.debug( "Got cache, ic = {0}", ic );
+
+                // remove this to the nowaits for this cachename
+                if ( ic != null )
+                {
+                    AuxiliaryCacheAttributes aca = ic.getAuxiliaryCacheAttributes();
+                    if (aca instanceof ITCPLateralCacheAttributes)
+                    {
+                        ITCPLateralCacheAttributes lca = (ITCPLateralCacheAttributes)aca;
+                        if (lca.getTransmissionType() != LateralCacheAttributes.Type.TCP
+                            || !serverAndPort.equals(lca.getTcpServer()) )
+                        {
+                            // skip caches not belonging to this service
+                            continue;
+                        }
+                    }
+
+                    removeNoWait( (LateralCacheNoWait<?, ?>) ic );
+                    log.debug( "Called removeNoWait for cacheName [{0}]", cacheName );
+                }
+            }
+        }
+        else
+        {
+            log.warn( "No cache names found in message {0}", service );
+        }
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPListener.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPListener.java
new file mode 100644
index 0000000..16d5c5e
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPListener.java
@@ -0,0 +1,677 @@
+package org.apache.commons.jcs3.auxiliary.lateral.socket.tcp;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.net.SocketTimeoutException;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.commons.jcs3.auxiliary.lateral.LateralElementDescriptor;
+import org.apache.commons.jcs3.auxiliary.lateral.behavior.ILateralCacheListener;
+import org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.behavior.ITCPLateralCacheAttributes;
+import org.apache.commons.jcs3.engine.CacheInfo;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
+import org.apache.commons.jcs3.engine.behavior.IShutdownObserver;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+import org.apache.commons.jcs3.io.ObjectInputStreamClassLoaderAware;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.threadpool.DaemonThreadFactory;
+
+/**
+ * Listens for connections from other TCP lateral caches and handles them. The initialization method
+ * starts a listening thread, which creates a socket server. When messages are received they are
+ * passed to a pooled executor which then calls the appropriate handle method.
+ */
+public class LateralTCPListener<K, V>
+    implements ILateralCacheListener<K, V>, IShutdownObserver
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog( LateralTCPListener.class );
+
+    /** How long the server will block on an accept(). 0 is infinite. */
+    private static final int acceptTimeOut = 1000;
+
+    /** The CacheHub this listener is associated with */
+    private transient ICompositeCacheManager cacheManager;
+
+    /** Map of available instances, keyed by port */
+    private static final ConcurrentHashMap<String, ILateralCacheListener<?, ?>> instances =
+        new ConcurrentHashMap<>();
+
+    /** The socket listener */
+    private ListenerThread receiver;
+
+    /** Configuration attributes */
+    private ITCPLateralCacheAttributes tcpLateralCacheAttributes;
+
+    /** The processor. We should probably use an event queue here. */
+    private ExecutorService pooledExecutor;
+
+    /** put count */
+    private int putCnt = 0;
+
+    /** remove count */
+    private int removeCnt = 0;
+
+    /** get count */
+    private int getCnt = 0;
+
+    /**
+     * Use the vmid by default. This can be set for testing. If we ever need to run more than one
+     * per vm, then we need a new technique.
+     */
+    private long listenerId = CacheInfo.listenerId;
+
+    /** is this shut down? */
+    private AtomicBoolean shutdown;
+
+    /** is this terminated? */
+    private AtomicBoolean terminated;
+
+    /**
+     * Gets the instance attribute of the LateralCacheTCPListener class.
+     * <p>
+     * @param ilca ITCPLateralCacheAttributes
+     * @param cacheMgr
+     * @return The instance value
+     */
+    public static <K, V> LateralTCPListener<K, V>
+        getInstance( ITCPLateralCacheAttributes ilca, ICompositeCacheManager cacheMgr )
+    {
+        @SuppressWarnings("unchecked") // Need to cast because of common map for all instances
+        LateralTCPListener<K, V> ins = (LateralTCPListener<K, V>) instances.computeIfAbsent(
+                String.valueOf( ilca.getTcpListenerPort() ),
+                k -> {
+                    LateralTCPListener<K, V> newIns = new LateralTCPListener<>( ilca );
+
+                    newIns.init();
+                    newIns.setCacheManager( cacheMgr );
+
+                    log.info( "Created new listener {0}",
+                            () -> ilca.getTcpListenerPort() );
+
+                    return newIns;
+                });
+
+        return ins;
+    }
+
+    /**
+     * Only need one since it does work for all regions, just reference by multiple region names.
+     * <p>
+     * @param ilca
+     */
+    protected LateralTCPListener( ITCPLateralCacheAttributes ilca )
+    {
+        this.setTcpLateralCacheAttributes( ilca );
+    }
+
+    /**
+     * This starts the ListenerThread on the specified port.
+     */
+    @Override
+    public synchronized void init()
+    {
+        try
+        {
+            int port = getTcpLateralCacheAttributes().getTcpListenerPort();
+            String host = getTcpLateralCacheAttributes().getTcpListenerHost();
+
+            pooledExecutor = Executors.newCachedThreadPool(
+                    new DaemonThreadFactory("JCS-LateralTCPListener-"));
+            terminated = new AtomicBoolean(false);
+            shutdown = new AtomicBoolean(false);
+
+            ServerSocket serverSocket;
+            if (host != null && host.length() > 0)
+            {
+                log.info( "Listening on {0}:{1}", host, port );
+                // Resolve host name
+                InetAddress inetAddress = InetAddress.getByName(host);
+                //Bind the SocketAddress with inetAddress and port
+                SocketAddress endPoint = new InetSocketAddress(inetAddress, port);
+
+                serverSocket = new ServerSocket();
+                serverSocket.bind(endPoint);
+            }
+            else
+            {
+                log.info( "Listening on port {0}", port );
+                serverSocket = new ServerSocket( port );
+            }
+            serverSocket.setSoTimeout( acceptTimeOut );
+
+            receiver = new ListenerThread(serverSocket);
+            receiver.setDaemon( true );
+            receiver.start();
+        }
+        catch ( IOException ex )
+        {
+            throw new IllegalStateException( ex );
+        }
+    }
+
+    /**
+     * Let the lateral cache set a listener_id. Since there is only one listener for all the
+     * regions and every region gets registered? the id shouldn't be set if it isn't zero. If it is
+     * we assume that it is a reconnect.
+     * <p>
+     * By default, the listener id is the vmid.
+     * <p>
+     * The service should set this value. This value will never be changed by a server we connect
+     * to. It needs to be non static, for unit tests.
+     * <p>
+     * The service will use the value it sets in all send requests to the sender.
+     * <p>
+     * @param id The new listenerId value
+     * @throws IOException
+     */
+    @Override
+    public void setListenerId( long id )
+        throws IOException
+    {
+        this.listenerId = id;
+        log.debug( "set listenerId = {0}", id );
+    }
+
+    /**
+     * Gets the listenerId attribute of the LateralCacheTCPListener object
+     * <p>
+     * @return The listenerId value
+     * @throws IOException
+     */
+    @Override
+    public long getListenerId()
+        throws IOException
+    {
+        return this.listenerId;
+    }
+
+    /**
+     * Increments the put count. Gets the cache that was injected by the lateral factory. Calls put
+     * on the cache.
+     * <p>
+     * @see org.apache.commons.jcs3.engine.behavior.ICacheListener#handlePut(org.apache.commons.jcs3.engine.behavior.ICacheElement)
+     */
+    @Override
+    public void handlePut( ICacheElement<K, V> element )
+        throws IOException
+    {
+        putCnt++;
+        if ( log.isInfoEnabled() && getPutCnt() % 100 == 0 )
+        {
+            log.info( "Put Count (port {0}) = {1}",
+                    () -> getTcpLateralCacheAttributes().getTcpListenerPort(),
+                    () -> getPutCnt() );
+        }
+
+        log.debug( "handlePut> cacheName={0}, key={1}",
+                () -> element.getCacheName(), () -> element.getKey() );
+
+        getCache( element.getCacheName() ).localUpdate( element );
+    }
+
+    /**
+     * Increments the remove count. Gets the cache that was injected by the lateral factory. Calls
+     * remove on the cache.
+     * <p>
+     * @see org.apache.commons.jcs3.engine.behavior.ICacheListener#handleRemove(java.lang.String,
+     *      Object)
+     */
+    @Override
+    public void handleRemove( String cacheName, K key )
+        throws IOException
+    {
+        removeCnt++;
+        if ( log.isInfoEnabled() && getRemoveCnt() % 100 == 0 )
+        {
+            log.info( "Remove Count = {0}", () -> getRemoveCnt() );
+        }
+
+        log.debug( "handleRemove> cacheName={0}, key={1}", cacheName, key );
+
+        getCache( cacheName ).localRemove( key );
+    }
+
+    /**
+     * Gets the cache that was injected by the lateral factory. Calls removeAll on the cache.
+     * <p>
+     * @see org.apache.commons.jcs3.engine.behavior.ICacheListener#handleRemoveAll(java.lang.String)
+     */
+    @Override
+    public void handleRemoveAll( String cacheName )
+        throws IOException
+    {
+        log.debug( "handleRemoveAll> cacheName={0}", cacheName );
+
+        getCache( cacheName ).localRemoveAll();
+    }
+
+    /**
+     * Gets the cache that was injected by the lateral factory. Calls get on the cache.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @return a ICacheElement
+     * @throws IOException
+     */
+    public ICacheElement<K, V> handleGet( String cacheName, K key )
+        throws IOException
+    {
+        getCnt++;
+        if ( log.isInfoEnabled() && getGetCnt() % 100 == 0 )
+        {
+            log.info( "Get Count (port {0}) = {1}",
+                    () -> getTcpLateralCacheAttributes().getTcpListenerPort(),
+                    () -> getGetCnt() );
+        }
+
+        log.debug( "handleGet> cacheName={0}, key={1}", cacheName, key );
+
+        return getCache( cacheName ).localGet( key );
+    }
+
+    /**
+     * Gets the cache that was injected by the lateral factory. Calls get on the cache.
+     * <p>
+     * @param cacheName the name of the cache
+     * @param pattern the matching pattern
+     * @return Map
+     * @throws IOException
+     */
+    public Map<K, ICacheElement<K, V>> handleGetMatching( String cacheName, String pattern )
+        throws IOException
+    {
+        getCnt++;
+        if ( log.isInfoEnabled() && getGetCnt() % 100 == 0 )
+        {
+            log.info( "GetMatching Count (port {0}) = {1}",
+                    () -> getTcpLateralCacheAttributes().getTcpListenerPort(),
+                    () -> getGetCnt() );
+        }
+
+        log.debug( "handleGetMatching> cacheName={0}, pattern={1}", cacheName, pattern );
+
+        return getCache( cacheName ).localGetMatching( pattern );
+    }
+
+    /**
+     * Gets the cache that was injected by the lateral factory. Calls getKeySet on the cache.
+     * <p>
+     * @param cacheName the name of the cache
+     * @return a set of keys
+     * @throws IOException
+     */
+    public Set<K> handleGetKeySet( String cacheName ) throws IOException
+    {
+    	return getCache( cacheName ).getKeySet(true);
+    }
+
+    /**
+     * This marks this instance as terminated.
+     * <p>
+     * @see org.apache.commons.jcs3.engine.behavior.ICacheListener#handleDispose(java.lang.String)
+     */
+    @Override
+    public void handleDispose( String cacheName )
+        throws IOException
+    {
+        log.info( "handleDispose > cacheName={0} | Ignoring message. "
+                + "Do not dispose from remote.", cacheName );
+
+        // TODO handle active deregistration, rather than passive detection
+        terminated.set(true);
+    }
+
+    @Override
+    public synchronized void dispose()
+    {
+        terminated.set(true);
+        notify();
+
+        pooledExecutor.shutdownNow();
+    }
+
+    /**
+     * Gets the cacheManager attribute of the LateralCacheTCPListener object.
+     * <p>
+     * Normally this is set by the factory. If it wasn't set the listener defaults to the expected
+     * singleton behavior of the cache manager.
+     * <p>
+     * @param name
+     * @return CompositeCache
+     */
+    protected CompositeCache<K, V> getCache( String name )
+    {
+        return getCacheManager().getCache( name );
+    }
+
+    /**
+     * This is roughly the number of updates the lateral has received.
+     * <p>
+     * @return Returns the putCnt.
+     */
+    public int getPutCnt()
+    {
+        return putCnt;
+    }
+
+    /**
+     * @return Returns the getCnt.
+     */
+    public int getGetCnt()
+    {
+        return getCnt;
+    }
+
+    /**
+     * @return Returns the removeCnt.
+     */
+    public int getRemoveCnt()
+    {
+        return removeCnt;
+    }
+
+    /**
+     * @param cacheMgr The cacheMgr to set.
+     */
+    @Override
+    public void setCacheManager( ICompositeCacheManager cacheMgr )
+    {
+        this.cacheManager = cacheMgr;
+    }
+
+    /**
+     * @return Returns the cacheMgr.
+     */
+    @Override
+    public ICompositeCacheManager getCacheManager()
+    {
+        return cacheManager;
+    }
+
+    /**
+     * @param tcpLateralCacheAttributes The tcpLateralCacheAttributes to set.
+     */
+    public void setTcpLateralCacheAttributes( ITCPLateralCacheAttributes tcpLateralCacheAttributes )
+    {
+        this.tcpLateralCacheAttributes = tcpLateralCacheAttributes;
+    }
+
+    /**
+     * @return Returns the tcpLateralCacheAttributes.
+     */
+    public ITCPLateralCacheAttributes getTcpLateralCacheAttributes()
+    {
+        return tcpLateralCacheAttributes;
+    }
+
+    /**
+     * Processes commands from the server socket. There should be one listener for each configured
+     * TCP lateral.
+     */
+    public class ListenerThread
+        extends Thread
+    {
+        /** The socket listener */
+        private final ServerSocket serverSocket;
+
+        /**
+         * Constructor
+         *
+         * @param serverSocket
+         */
+        public ListenerThread(ServerSocket serverSocket)
+        {
+            super();
+            this.serverSocket = serverSocket;
+        }
+
+        /** Main processing method for the ListenerThread object */
+        @SuppressWarnings("synthetic-access")
+        @Override
+        public void run()
+        {
+            try (ServerSocket ssck = serverSocket)
+            {
+                ConnectionHandler handler;
+
+                outer: while ( true )
+                {
+                    log.debug( "Waiting for clients to connect " );
+
+                    Socket socket = null;
+                    inner: while (true)
+                    {
+                        // Check to see if we've been asked to exit, and exit
+                        if (terminated.get())
+                        {
+                            log.debug("Thread terminated, exiting gracefully");
+                            break outer;
+                        }
+
+                        try
+                        {
+                            socket = ssck.accept();
+                            break inner;
+                        }
+                        catch (SocketTimeoutException e)
+                        {
+                            // No problem! We loop back up!
+                            continue inner;
+                        }
+                    }
+
+                    if ( socket != null && log.isDebugEnabled() )
+                    {
+                        InetAddress inetAddress = socket.getInetAddress();
+                        log.debug( "Connected to client at {0}", inetAddress );
+                    }
+
+                    handler = new ConnectionHandler( socket );
+                    pooledExecutor.execute( handler );
+                }
+            }
+            catch ( IOException e )
+            {
+                log.error( "Exception caught in TCP listener", e );
+            }
+        }
+    }
+
+    /**
+     * A Separate thread that runs when a command comes into the LateralTCPReceiver.
+     */
+    public class ConnectionHandler
+        implements Runnable
+    {
+        /** The socket connection, passed in via constructor */
+        private final Socket socket;
+
+        /**
+         * Construct for a given socket
+         * @param socket
+         */
+        public ConnectionHandler( Socket socket )
+        {
+            this.socket = socket;
+        }
+
+        /**
+         * Main processing method for the LateralTCPReceiverConnection object
+         */
+        @Override
+        @SuppressWarnings({"unchecked", // Need to cast from Object
+            "synthetic-access" })
+        public void run()
+        {
+            try (ObjectInputStream ois =
+                    new ObjectInputStreamClassLoaderAware( socket.getInputStream(), null ))
+            {
+                while ( true )
+                {
+                    LateralElementDescriptor<K, V> led =
+                            (LateralElementDescriptor<K, V>) ois.readObject();
+
+                    if ( led == null )
+                    {
+                        log.debug( "LateralElementDescriptor is null" );
+                        continue;
+                    }
+                    if ( led.requesterId == getListenerId() )
+                    {
+                        log.debug( "from self" );
+                    }
+                    else
+                    {
+                        log.debug( "receiving LateralElementDescriptor from another led = {0}",
+                                led );
+
+                        handle( led );
+                    }
+                }
+            }
+            catch ( EOFException e )
+            {
+                log.info( "Caught EOFException, closing connection.", e );
+            }
+            catch ( SocketException e )
+            {
+                log.info( "Caught SocketException, closing connection.", e );
+            }
+            catch ( Exception e )
+            {
+                log.error( "Unexpected exception.", e );
+            }
+        }
+
+        /**
+         * This calls the appropriate method, based on the command sent in the Lateral element
+         * descriptor.
+         * <p>
+         * @param led
+         * @throws IOException
+         */
+        @SuppressWarnings("synthetic-access")
+        private void handle( LateralElementDescriptor<K, V> led )
+            throws IOException
+        {
+            String cacheName = led.ce.getCacheName();
+            K key = led.ce.getKey();
+            Serializable obj = null;
+
+            switch (led.command)
+            {
+                case UPDATE:
+                    handlePut( led.ce );
+                    break;
+
+                case REMOVE:
+                    // if a hashcode was given and filtering is on
+                    // check to see if they are the same
+                    // if so, then don't remove, otherwise issue a remove
+                    if ( led.valHashCode != -1 )
+                    {
+                        if ( getTcpLateralCacheAttributes().isFilterRemoveByHashCode() )
+                        {
+                            ICacheElement<K, V> test = getCache( cacheName ).localGet( key );
+                            if ( test != null )
+                            {
+                                if ( test.getVal().hashCode() == led.valHashCode )
+                                {
+                                    log.debug( "Filtering detected identical hashCode [{0}], "
+                                            + "not issuing a remove for led {1}",
+                                            led.valHashCode, led );
+                                    return;
+                                }
+                                else
+                                {
+                                    log.debug( "Different hashcodes, in cache [{0}] sent [{1}]",
+                                            test.getVal().hashCode(), led.valHashCode );
+                                }
+                            }
+                        }
+                    }
+                    handleRemove( cacheName, key );
+                    break;
+
+                case REMOVEALL:
+                    handleRemoveAll( cacheName );
+                    break;
+
+                case GET:
+                    obj = handleGet( cacheName, key );
+                    break;
+
+                case GET_MATCHING:
+                    obj = (Serializable) handleGetMatching( cacheName, (String) key );
+                    break;
+
+                case GET_KEYSET:
+                	obj = (Serializable) handleGetKeySet(cacheName);
+                    break;
+
+                default: break;
+            }
+
+            if (obj != null)
+            {
+                ObjectOutputStream oos = new ObjectOutputStream( socket.getOutputStream() );
+                oos.writeObject( obj );
+                oos.flush();
+            }
+        }
+    }
+
+    /**
+     * Shuts down the receiver.
+     */
+    @Override
+    public void shutdown()
+    {
+        if ( shutdown.compareAndSet(false, true) )
+        {
+            log.info( "Shutting down TCP Lateral receiver." );
+
+            receiver.interrupt();
+        }
+        else
+        {
+            log.debug( "Shutdown already called." );
+        }
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPSender.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPSender.java
new file mode 100644
index 0000000..3f42065
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPSender.java
@@ -0,0 +1,259 @@
+package org.apache.commons.jcs3.auxiliary.lateral.socket.tcp;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+
+import org.apache.commons.jcs3.auxiliary.lateral.LateralElementDescriptor;
+import org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.behavior.ITCPLateralCacheAttributes;
+import org.apache.commons.jcs3.io.ObjectInputStreamClassLoaderAware;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * This class is based on the log4j SocketAppender class. I'm using a different repair structure, so
+ * it is significantly different.
+ */
+public class LateralTCPSender
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog( LateralTCPSender.class );
+
+    /** Config */
+    private final int socketOpenTimeOut;
+    private final int socketSoTimeOut;
+
+    /** The stream from the server connection. */
+    private ObjectOutputStream oos;
+
+    /** The socket connection with the server. */
+    private Socket socket;
+
+    /** how many messages sent */
+    private int sendCnt = 0;
+
+    /** Use to synchronize multiple threads that may be trying to get. */
+    private final Object getLock = new int[0];
+
+    /**
+     * Constructor for the LateralTCPSender object.
+     * <p>
+     * @param lca
+     * @throws IOException
+     */
+    public LateralTCPSender( ITCPLateralCacheAttributes lca )
+        throws IOException
+    {
+        this.socketOpenTimeOut = lca.getOpenTimeOut();
+        this.socketSoTimeOut = lca.getSocketTimeOut();
+
+        String p1 = lca.getTcpServer();
+        if ( p1 == null )
+        {
+            throw new IOException( "Invalid server (null)" );
+        }
+
+        String h2 = p1.substring( 0, p1.indexOf( ":" ) );
+        int po = Integer.parseInt( p1.substring( p1.indexOf( ":" ) + 1 ) );
+        log.debug( "h2 = {0}, po = {1}", h2, po );
+
+        if ( h2.length() == 0 )
+        {
+            throw new IOException( "Cannot connect to invalid address [" + h2 + ":" + po + "]" );
+        }
+
+        init( h2, po );
+    }
+
+    /**
+     * Creates a connection to a TCP server.
+     * <p>
+     * @param host
+     * @param port
+     * @throws IOException
+     */
+    protected void init( String host, int port )
+        throws IOException
+    {
+        try
+        {
+            log.info( "Attempting connection to [{0}]", host );
+
+            // have time out socket open do this for us
+            try
+            {
+                socket = new Socket();
+                socket.connect( new InetSocketAddress( host, port ), this.socketOpenTimeOut );
+            }
+            catch ( IOException ioe )
+            {
+                if (socket != null)
+                {
+                    socket.close();
+                }
+
+                throw new IOException( "Cannot connect to " + host + ":" + port, ioe );
+            }
+
+            socket.setSoTimeout( socketSoTimeOut );
+            synchronized ( this )
+            {
+                oos = new ObjectOutputStream( socket.getOutputStream() );
+            }
+        }
+        catch ( java.net.ConnectException e )
+        {
+            log.debug( "Remote host [{0}] refused connection.", host );
+            throw e;
+        }
+        catch ( IOException e )
+        {
+            log.debug( "Could not connect to [{0}]", host, e );
+            throw e;
+        }
+    }
+
+    /**
+     * Sends commands to the lateral cache listener.
+     * <p>
+     * @param led
+     * @throws IOException
+     */
+    public <K, V> void send( LateralElementDescriptor<K, V> led )
+        throws IOException
+    {
+        sendCnt++;
+        if ( log.isInfoEnabled() && sendCnt % 100 == 0 )
+        {
+            log.info( "Send Count (port {0}) = {1}", socket.getPort(), sendCnt );
+        }
+
+        log.debug( "sending LateralElementDescriptor" );
+
+        if ( led == null )
+        {
+            return;
+        }
+
+        if ( oos == null )
+        {
+            throw new IOException( "No remote connection is available for LateralTCPSender." );
+        }
+
+        synchronized ( this.getLock )
+        {
+            oos.writeUnshared( led );
+            oos.flush();
+        }
+    }
+
+    /**
+     * Sends commands to the lateral cache listener and gets a response. I'm afraid that we could
+     * get into a pretty bad blocking situation here. This needs work. I just wanted to get some
+     * form of get working. However, get is not recommended for performance reasons. If you have 10
+     * laterals, then you have to make 10 failed gets to find out none of the caches have the item.
+     * <p>
+     * @param led
+     * @return ICacheElement
+     * @throws IOException
+     */
+    public <K, V> Object sendAndReceive( LateralElementDescriptor<K, V> led )
+        throws IOException
+    {
+        if ( led == null )
+        {
+            return null;
+        }
+
+        if ( oos == null )
+        {
+            throw new IOException( "No remote connection is available for LateralTCPSender." );
+        }
+
+        Object response = null;
+
+        // Synchronized to insure that the get requests to server from this
+        // sender and the responses are processed in order, else you could
+        // return the wrong item from the cache.
+        // This is a big block of code. May need to re-think this strategy.
+        // This may not be necessary.
+        // Normal puts, etc to laterals do not have to be synchronized.
+        synchronized ( this.getLock )
+        {
+            try
+            {
+                // clean up input stream, nothing should be there yet.
+                if ( socket.getInputStream().available() > 0 )
+                {
+                    socket.getInputStream().read( new byte[socket.getInputStream().available()] );
+                }
+            }
+            catch ( IOException ioe )
+            {
+                log.error( "Problem cleaning socket before send {0}", socket, ioe );
+                throw ioe;
+            }
+
+            // write object to listener
+            oos.writeUnshared( led );
+            oos.flush();
+
+            try (ObjectInputStream ois = new ObjectInputStreamClassLoaderAware( socket.getInputStream(), null ))
+            {
+                socket.setSoTimeout( socketSoTimeOut );
+                response = ois.readObject();
+            }
+            catch ( IOException ioe )
+            {
+                String message = "Could not open ObjectInputStream to " + socket +
+                    " SoTimeout [" + socket.getSoTimeout() +
+                    "] Connected [" + socket.isConnected() + "]";
+                log.error( message, ioe );
+                throw ioe;
+            }
+            catch ( Exception e )
+            {
+                log.error( e );
+            }
+        }
+
+        return response;
+    }
+
+    /**
+     * Closes connection used by all LateralTCPSenders for this lateral connection. Dispose request
+     * should come into the facade and be sent to all lateral cache services. The lateral cache
+     * service will then call this method.
+     * <p>
+     * @throws IOException
+     */
+    public void dispose()
+        throws IOException
+    {
+        log.info( "Dispose called" );
+        // WILL CLOSE CONNECTION USED BY ALL
+        oos.close();
+        socket.close();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPService.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPService.java
new file mode 100644
index 0000000..81f4eb2
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPService.java
@@ -0,0 +1,451 @@
+package org.apache.commons.jcs3.auxiliary.lateral.socket.tcp;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.auxiliary.lateral.LateralCommand;
+import org.apache.commons.jcs3.auxiliary.lateral.LateralElementDescriptor;
+import org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.behavior.ITCPLateralCacheAttributes;
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.CacheInfo;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheServiceNonLocal;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * A lateral cache service implementation. Does not implement getGroupKey
+ * TODO: Remove generics
+ */
+public class LateralTCPService<K, V>
+    implements ICacheServiceNonLocal<K, V>
+{
+    /** The logger. */
+    private static final Log log = LogManager.getLog( LateralTCPService.class );
+
+    /** special configuration */
+    private final boolean allowPut;
+    private final boolean allowGet;
+    private final boolean issueRemoveOnPut;
+
+    /** Sends to another lateral. */
+    private LateralTCPSender sender;
+
+    /** use the vmid by default */
+    private long listenerId = CacheInfo.listenerId;
+
+    /**
+     * Constructor for the LateralTCPService object
+     * <p>
+     * @param lca ITCPLateralCacheAttributes
+     * @throws IOException
+     */
+    public LateralTCPService( ITCPLateralCacheAttributes lca )
+        throws IOException
+    {
+        this.allowGet = lca.isAllowGet();
+        this.allowPut = lca.isAllowPut();
+        this.issueRemoveOnPut = lca.isIssueRemoveOnPut();
+
+        try
+        {
+            sender = new LateralTCPSender( lca );
+
+            log.debug( "Created sender to [{0}]", () -> lca.getTcpServer() );
+        }
+        catch ( IOException e )
+        {
+            // log.error( "Could not create sender", e );
+            // This gets thrown over and over in recovery mode.
+            // The stack trace isn't useful here.
+            log.error( "Could not create sender to [{0}] -- {1}", lca.getTcpServer(), e.getMessage());
+            throw e;
+        }
+    }
+
+    /**
+     * @param item
+     * @throws IOException
+     */
+    @Override
+    public void update( ICacheElement<K, V> item )
+        throws IOException
+    {
+        update( item, getListenerId() );
+    }
+
+    /**
+     * If put is allowed, we will issue a put. If issue put on remove is configured, we will issue a
+     * remove. Either way, we create a lateral element descriptor, which is essentially a JCS TCP
+     * packet. It describes what operation the receiver should take when it gets the packet.
+     * <p>
+     * @see org.apache.commons.jcs3.engine.behavior.ICacheServiceNonLocal#update(org.apache.commons.jcs3.engine.behavior.ICacheElement,
+     *      long)
+     */
+    @Override
+    public void update( ICacheElement<K, V> item, long requesterId )
+        throws IOException
+    {
+        // if we don't allow put, see if we should remove on put
+        if ( !this.allowPut &&
+            // if we can't remove on put, and we can't put then return
+            !this.issueRemoveOnPut )
+        {
+            return;
+        }
+
+        // if we shouldn't remove on put, then put
+        if ( !this.issueRemoveOnPut )
+        {
+            LateralElementDescriptor<K, V> led = new LateralElementDescriptor<>( item );
+            led.requesterId = requesterId;
+            led.command = LateralCommand.UPDATE;
+            sender.send( led );
+        }
+        // else issue a remove with the hashcode for remove check on
+        // on the other end, this will be a server config option
+        else
+        {
+            log.debug( "Issuing a remove for a put" );
+
+            // set the value to null so we don't send the item
+            CacheElement<K, V> ce = new CacheElement<>( item.getCacheName(), item.getKey(), null );
+            LateralElementDescriptor<K, V> led = new LateralElementDescriptor<>( ce );
+            led.requesterId = requesterId;
+            led.command = LateralCommand.REMOVE;
+            led.valHashCode = item.getVal().hashCode();
+            sender.send( led );
+        }
+    }
+
+    /**
+     * Uses the default listener id and calls the next remove method.
+     * <p>
+     * @see org.apache.commons.jcs3.engine.behavior.ICacheService#remove(String, Object)
+     */
+    @Override
+    public void remove( String cacheName, K key )
+        throws IOException
+    {
+        remove( cacheName, key, getListenerId() );
+    }
+
+    /**
+     * Wraps the key in a LateralElementDescriptor.
+     * <p>
+     * @see org.apache.commons.jcs3.engine.behavior.ICacheServiceNonLocal#remove(String, Object, long)
+     */
+    @Override
+    public void remove( String cacheName, K key, long requesterId )
+        throws IOException
+    {
+        CacheElement<K, V> ce = new CacheElement<>( cacheName, key, null );
+        LateralElementDescriptor<K, V> led = new LateralElementDescriptor<>( ce );
+        led.requesterId = requesterId;
+        led.command = LateralCommand.REMOVE;
+        sender.send( led );
+    }
+
+    /**
+     * Does nothing.
+     * <p>
+     * @throws IOException
+     */
+    @Override
+    public void release()
+        throws IOException
+    {
+        // nothing needs to be done
+    }
+
+    /**
+     * Will close the connection.
+     * <p>
+     * @param cacheName
+     * @throws IOException
+     */
+    @Override
+    public void dispose( String cacheName )
+        throws IOException
+    {
+        sender.dispose();
+    }
+
+    /**
+     * @param cacheName
+     * @param key
+     * @return ICacheElement&lt;K, V&gt; if found.
+     * @throws IOException
+     */
+    @Override
+    public ICacheElement<K, V> get( String cacheName, K key )
+        throws IOException
+    {
+        return get( cacheName, key, getListenerId() );
+    }
+
+    /**
+     * If get is allowed, we will issues a get request.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @param requesterId
+     * @return ICacheElement&lt;K, V&gt; if found.
+     * @throws IOException
+     */
+    @Override
+    public ICacheElement<K, V> get( String cacheName, K key, long requesterId )
+        throws IOException
+    {
+        // if get is not allowed return
+        if ( this.allowGet )
+        {
+            CacheElement<K, V> ce = new CacheElement<>( cacheName, key, null );
+            LateralElementDescriptor<K, V> led = new LateralElementDescriptor<>( ce );
+            // led.requesterId = requesterId; // later
+            led.command = LateralCommand.GET;
+            @SuppressWarnings("unchecked") // Need to cast from Object
+            ICacheElement<K, V> response = (ICacheElement<K, V>)sender.sendAndReceive( led );
+            if ( response != null )
+            {
+                return response;
+            }
+            return null;
+        }
+        else
+        {
+            // nothing needs to be done
+            return null;
+        }
+    }
+
+    /**
+     * If allow get is true, we will issue a getmatching query.
+     * <p>
+     * @param cacheName
+     * @param pattern
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache matching the pattern.
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern )
+        throws IOException
+    {
+        return getMatching( cacheName, pattern, getListenerId() );
+    }
+
+    /**
+     * If allow get is true, we will issue a getmatching query.
+     * <p>
+     * @param cacheName
+     * @param pattern
+     * @param requesterId - our identity
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache matching the pattern.
+     * @throws IOException
+     */
+    @Override
+    @SuppressWarnings("unchecked") // Need to cast from Object
+    public Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern, long requesterId )
+        throws IOException
+    {
+        // if get is not allowed return
+        if ( this.allowGet )
+        {
+            CacheElement<String, String> ce = new CacheElement<>( cacheName, pattern, null );
+            LateralElementDescriptor<String, String> led = new LateralElementDescriptor<>( ce );
+            // led.requesterId = requesterId; // later
+            led.command = LateralCommand.GET_MATCHING;
+
+            Object response = sender.sendAndReceive( led );
+            if ( response != null )
+            {
+                return (Map<K, ICacheElement<K, V>>) response;
+            }
+            return Collections.emptyMap();
+        }
+        else
+        {
+            // nothing needs to be done
+            return null;
+        }
+    }
+
+    /**
+     * Gets multiple items from the cache based on the given set of keys.
+     * <p>
+     * @param cacheName
+     * @param keys
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache for any of these keys
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys )
+        throws IOException
+    {
+        return getMultiple( cacheName, keys, getListenerId() );
+    }
+
+    /**
+     * This issues a separate get for each item.
+     * <p>
+     * TODO We should change this. It should issue one request.
+     * <p>
+     * @param cacheName
+     * @param keys
+     * @param requesterId
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache for any of these keys
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys, long requesterId )
+        throws IOException
+    {
+        Map<K, ICacheElement<K, V>> elements = new HashMap<>();
+
+        if ( keys != null && !keys.isEmpty() )
+        {
+            for (K key : keys)
+            {
+                ICacheElement<K, V> element = get( cacheName, key );
+
+                if ( element != null )
+                {
+                    elements.put( key, element );
+                }
+            }
+        }
+        return elements;
+    }
+
+    /**
+     * Return the keys in this cache.
+     * <p>
+     * @param cacheName the name of the cache region
+     * @see org.apache.commons.jcs3.auxiliary.AuxiliaryCache#getKeySet()
+     */
+    @Override
+    @SuppressWarnings("unchecked") // Need cast from Object
+    public Set<K> getKeySet(String cacheName) throws IOException
+    {
+        CacheElement<String, String> ce = new CacheElement<>(cacheName, null, null);
+        LateralElementDescriptor<String, String> led = new LateralElementDescriptor<>(ce);
+        // led.requesterId = requesterId; // later
+        led.command = LateralCommand.GET_KEYSET;
+        Object response = sender.sendAndReceive(led);
+        if (response != null)
+        {
+            return (Set<K>) response;
+        }
+
+        return null;
+    }
+
+    /**
+     * @param cacheName
+     * @throws IOException
+     */
+    @Override
+    public void removeAll( String cacheName )
+        throws IOException
+    {
+        removeAll( cacheName, getListenerId() );
+    }
+
+    /**
+     * @param cacheName
+     * @param requesterId
+     * @throws IOException
+     */
+    @Override
+    public void removeAll( String cacheName, long requesterId )
+        throws IOException
+    {
+        CacheElement<String, String> ce = new CacheElement<>( cacheName, "ALL", null );
+        LateralElementDescriptor<String, String> led = new LateralElementDescriptor<>( ce );
+        led.requesterId = requesterId;
+        led.command = LateralCommand.REMOVEALL;
+        sender.send( led );
+    }
+
+    /**
+     * @param args
+     */
+    public static void main( String args[] )
+    {
+        try
+        {
+            LateralTCPSender sender = new LateralTCPSender( new TCPLateralCacheAttributes() );
+
+            // process user input till done
+            boolean notDone = true;
+            String message = null;
+            // wait to dispose
+            BufferedReader br = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8));
+
+            while ( notDone )
+            {
+                System.out.println( "enter message:" );
+                message = br.readLine();
+
+                if (message == null)
+                {
+                    notDone = false;
+                    continue;
+                }
+
+                CacheElement<String, String> ce = new CacheElement<>( "test", "test", message );
+                LateralElementDescriptor<String, String> led = new LateralElementDescriptor<>( ce );
+                sender.send( led );
+            }
+        }
+        catch ( IOException e )
+        {
+            System.out.println( e.toString() );
+        }
+    }
+
+    /**
+     * @param listernId The listernId to set.
+     */
+    protected void setListenerId( long listernId )
+    {
+        this.listenerId = listernId;
+    }
+
+    /**
+     * @return Returns the listernId.
+     */
+    protected long getListenerId()
+    {
+        return listenerId;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/TCPLateralCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/TCPLateralCacheAttributes.java
new file mode 100644
index 0000000..9fcb5c6
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/TCPLateralCacheAttributes.java
@@ -0,0 +1,407 @@
+package org.apache.commons.jcs3.auxiliary.lateral.socket.tcp;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.behavior.ITCPLateralCacheAttributes;
+
+/**
+ * This interface defines functions that are particular to the TCP Lateral Cache plugin. It extends
+ * the generic LateralCacheAttributes interface which in turn extends the AuxiliaryCache interface.
+ */
+public class TCPLateralCacheAttributes
+    extends LateralCacheAttributes
+    implements ITCPLateralCacheAttributes
+{
+    /** Don't change. */
+    private static final long serialVersionUID = 1077889204513905220L;
+
+    /** default */
+    private static final String DEFAULT_UDP_DISCOVERY_ADDRESS = "228.5.6.7";
+
+    /** default */
+    private static final int DEFAULT_UDP_DISCOVERY_PORT = 6789;
+
+    /** default */
+    private static final boolean DEFAULT_UDP_DISCOVERY_ENABLED = true;
+
+    /** default */
+    private static final boolean DEFAULT_ALLOW_GET = true;
+
+    /** default */
+    private static final boolean DEFAULT_ALLOW_PUT = true;
+
+    /** default */
+    private static final boolean DEFAULT_ISSUE_REMOVE_FOR_PUT = false;
+
+    /** default */
+    private static final boolean DEFAULT_FILTER_REMOVE_BY_HASH_CODE = true;
+
+    /** default - Only block for 1 second before timing out on a read.*/
+    private static final int DEFAULT_SOCKET_TIME_OUT = 1000;
+
+    /** default - Only block for 2 seconds before timing out on startup.*/
+    private static final int DEFAULT_OPEN_TIMEOUT = 2000;
+
+    /** TCP -------------------------------------------- */
+    private String tcpServers = "";
+
+    /** used to identify the service that this manager will be operating on */
+    private String tcpServer = "";
+
+    /** The port */
+    private int tcpListenerPort = 0;
+
+    /** The host */
+    private String tcpListenerHost = "";
+
+    /** udp discovery for tcp server */
+    private String udpDiscoveryAddr = DEFAULT_UDP_DISCOVERY_ADDRESS;
+
+    /** discovery port */
+    private int udpDiscoveryPort = DEFAULT_UDP_DISCOVERY_PORT;
+
+    /** discovery switch */
+    private boolean udpDiscoveryEnabled = DEFAULT_UDP_DISCOVERY_ENABLED;
+
+    /** can we put */
+    private boolean allowPut = DEFAULT_ALLOW_GET;
+
+    /** can we go laterally for a get */
+    private boolean allowGet = DEFAULT_ALLOW_PUT;
+
+    /** call remove when there is a put */
+    private boolean issueRemoveOnPut = DEFAULT_ISSUE_REMOVE_FOR_PUT;
+
+    /** don't remove it the hashcode is the same */
+    private boolean filterRemoveByHashCode = DEFAULT_FILTER_REMOVE_BY_HASH_CODE;
+
+    /** Only block for socketTimeOut seconds before timing out on a read.  */
+    private int socketTimeOut = DEFAULT_SOCKET_TIME_OUT;
+
+    /** Only block for openTimeOut seconds before timing out on startup. */
+    private int openTimeOut = DEFAULT_OPEN_TIMEOUT;
+
+    /**
+     * Sets the tcpServer attribute of the ILateralCacheAttributes object
+     * <p>
+     * @param val The new tcpServer value
+     */
+    @Override
+    public void setTcpServer( String val )
+    {
+        this.tcpServer = val;
+    }
+
+    /**
+     * Gets the tcpServer attribute of the ILateralCacheAttributes object
+     * <p>
+     * @return The tcpServer value
+     */
+    @Override
+    public String getTcpServer()
+    {
+        return this.tcpServer;
+    }
+
+    /**
+     * Sets the tcpServers attribute of the ILateralCacheAttributes object
+     * <p>
+     * @param val The new tcpServers value
+     */
+    @Override
+    public void setTcpServers( String val )
+    {
+        this.tcpServers = val;
+    }
+
+    /**
+     * Gets the tcpServers attribute of the ILateralCacheAttributes object
+     * <p>
+     * @return The tcpServers value
+     */
+    @Override
+    public String getTcpServers()
+    {
+        return this.tcpServers;
+    }
+
+    /**
+     * Sets the tcpListenerPort attribute of the ILateralCacheAttributes object
+     * <p>
+     * @param val The new tcpListenerPort value
+     */
+    @Override
+    public void setTcpListenerPort( int val )
+    {
+        this.tcpListenerPort = val;
+    }
+
+    /**
+     * Gets the tcpListenerPort attribute of the ILateralCacheAttributes object
+     * <p>
+     * @return The tcpListenerPort value
+     */
+    @Override
+    public int getTcpListenerPort()
+    {
+        return this.tcpListenerPort;
+    }
+
+    /**
+     * Sets the tcpListenerHost attribute of the ILateralCacheAttributes object
+     * <p>
+     * @param val
+     *            The new tcpListenerHost value
+     */
+    @Override
+    public void setTcpListenerHost( String val )
+    {
+        this.tcpListenerHost = val;
+    }
+
+    /**
+     * Gets the tcpListenerHost attribute of the ILateralCacheAttributes object
+     * <p>
+     * @return The tcpListenerHost value
+     */
+    @Override
+    public String getTcpListenerHost()
+    {
+        return this.tcpListenerHost;
+    }
+
+    /**
+     * Can setup UDP Discovery. This only works for TCp laterals right now. It allows TCP laterals
+     * to find each other by broadcasting to a multicast port.
+     * <p>
+     * @param udpDiscoveryEnabled The udpDiscoveryEnabled to set.
+     */
+    @Override
+    public void setUdpDiscoveryEnabled( boolean udpDiscoveryEnabled )
+    {
+        this.udpDiscoveryEnabled = udpDiscoveryEnabled;
+    }
+
+    /**
+     * Whether or not TCP laterals can try to find each other by multicast communication.
+     * <p>
+     * @return Returns the udpDiscoveryEnabled.
+     */
+    @Override
+    public boolean isUdpDiscoveryEnabled()
+    {
+        return this.udpDiscoveryEnabled;
+    }
+
+    /**
+     * The port to use if UDPDiscovery is enabled.
+     * <p>
+     * @return Returns the udpDiscoveryPort.
+     */
+    @Override
+    public int getUdpDiscoveryPort()
+    {
+        return this.udpDiscoveryPort;
+    }
+
+    /**
+     * Sets the port to use if UDPDiscovery is enabled.
+     * <p>
+     * @param udpDiscoveryPort The udpDiscoveryPort to set.
+     */
+    @Override
+    public void setUdpDiscoveryPort( int udpDiscoveryPort )
+    {
+        this.udpDiscoveryPort = udpDiscoveryPort;
+    }
+
+    /**
+     * The address to broadcast to if UDPDiscovery is enabled.
+     * <p>
+     * @return Returns the udpDiscoveryAddr.
+     */
+    @Override
+    public String getUdpDiscoveryAddr()
+    {
+        return this.udpDiscoveryAddr;
+    }
+
+    /**
+     * Sets the address to broadcast to if UDPDiscovery is enabled.
+     * <p>
+     * @param udpDiscoveryAddr The udpDiscoveryAddr to set.
+     */
+    @Override
+    public void setUdpDiscoveryAddr( String udpDiscoveryAddr )
+    {
+        this.udpDiscoveryAddr = udpDiscoveryAddr;
+    }
+
+    /**
+     * Is the lateral allowed to try and get from other laterals.
+     * <p>
+     * This replaces the old putOnlyMode
+     * <p>
+     * @param allowGet
+     */
+    @Override
+    public void setAllowGet( boolean allowGet )
+    {
+        this.allowGet = allowGet;
+    }
+
+    /**
+     * Is the lateral allowed to try and get from other laterals.
+     * <p>
+     * @return true if the lateral will try to get
+     */
+    @Override
+    public boolean isAllowGet()
+    {
+        return this.allowGet;
+    }
+
+    /**
+     * Is the lateral allowed to put objects to other laterals.
+     * <p>
+     * @param allowPut
+     */
+    @Override
+    public void setAllowPut( boolean allowPut )
+    {
+        this.allowPut = allowPut;
+    }
+
+    /**
+     * Is the lateral allowed to put objects to other laterals.
+     * <p>
+     * @return true if puts are allowed
+     */
+    @Override
+    public boolean isAllowPut()
+    {
+        return this.allowPut;
+    }
+
+    /**
+     * Should the client send a remove command rather than a put when update is called. This is a
+     * client option, not a receiver option. This allows you to prevent the lateral from serializing
+     * objects.
+     * <p>
+     * @param issueRemoveOnPut
+     */
+    @Override
+    public void setIssueRemoveOnPut( boolean issueRemoveOnPut )
+    {
+        this.issueRemoveOnPut = issueRemoveOnPut;
+    }
+
+    /**
+     * Should the client send a remove command rather than a put when update is called. This is a
+     * client option, not a receiver option. This allows you to prevent the lateral from serializing
+     * objects.
+     * <p>
+     * @return true if updates will result in a remove command being sent.
+     */
+    @Override
+    public boolean isIssueRemoveOnPut()
+    {
+        return this.issueRemoveOnPut;
+    }
+
+    /**
+     * Should the receiver try to match hashcodes. If true, the receiver will see if the client
+     * supplied a hashcode. If it did, then it will try to get the item locally. If the item exists,
+     * then it will compare the hashcode. if they are the same, it will not remove. This isn't
+     * perfect since different objects can have the same hashcode, but it is unlikely of objects of
+     * the same type.
+     * <p>
+     * @return boolean
+     */
+    @Override
+    public boolean isFilterRemoveByHashCode()
+    {
+        return this.filterRemoveByHashCode;
+    }
+
+    /**
+     * Should the receiver try to match hashcodes. If true, the receiver will see if the client
+     * supplied a hashcode. If it did, then it will try to get the item locally. If the item exists,
+     * then it will compare the hashcode. if they are the same, it will not remove. This isn't
+     * perfect since different objects can have the same hashcode, but it is unlikely of objects of
+     * the same type.
+     * <p>
+     * @param filter
+     */
+    @Override
+    public void setFilterRemoveByHashCode( boolean filter )
+    {
+        this.filterRemoveByHashCode = filter;
+    }
+
+    /**
+     * @param socketTimeOut the socketTimeOut to set
+     */
+    @Override
+    public void setSocketTimeOut( int socketTimeOut )
+    {
+        this.socketTimeOut = socketTimeOut;
+    }
+
+    /**
+     * @return the socketTimeOut
+     */
+    @Override
+    public int getSocketTimeOut()
+    {
+        return socketTimeOut;
+    }
+
+    /**
+     * @param openTimeOut the openTimeOut to set
+     */
+    @Override
+    public void setOpenTimeOut( int openTimeOut )
+    {
+        this.openTimeOut = openTimeOut;
+    }
+
+    /**
+     * @return the openTimeOut
+     */
+    @Override
+    public int getOpenTimeOut()
+    {
+        return openTimeOut;
+    }
+
+    /**
+     * Used to key the instance TODO create another method for this and use toString for debugging
+     * only.
+     * <p>
+     * @return String
+     */
+    @Override
+    public String toString()
+    {
+        return this.getTcpServer() + ":" + this.getTcpListenerPort();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/behavior/ITCPLateralCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/behavior/ITCPLateralCacheAttributes.java
new file mode 100644
index 0000000..ce4569f
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/behavior/ITCPLateralCacheAttributes.java
@@ -0,0 +1,233 @@
+package org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.auxiliary.lateral.behavior.ILateralCacheAttributes;
+
+/**
+ * This interface defines functions that are particular to the TCP Lateral Cache
+ * plugin. It extends the generic LateralCacheAttributes interface which in turn
+ * extends the AuxiliaryCache interface.
+ * <p>
+ * @author Aaron Smuts
+ */
+public interface ITCPLateralCacheAttributes
+    extends ILateralCacheAttributes
+{
+    /**
+     * Sets the tcpServer attribute of the ILateralCacheAttributes object
+     * <p>
+     * @param val
+     *            The new tcpServer value
+     */
+    void setTcpServer( String val );
+
+    /**
+     * Gets the tcpServer attribute of the ILateralCacheAttributes object
+     * <p>
+     * @return The tcpServer value
+     */
+    String getTcpServer();
+
+    /**
+     * Sets the tcpServers attribute of the ILateralCacheAttributes object
+     * <p>
+     * @param val
+     *            The new tcpServers value
+     */
+    void setTcpServers( String val );
+
+    /**
+     * Gets the tcpServers attribute of the ILateralCacheAttributes object
+     * <p>
+     * @return The tcpServers value
+     */
+    String getTcpServers();
+
+    /**
+     * Sets the tcpListenerPort attribute of the ILateralCacheAttributes object
+     * <p>
+     * @param val
+     *            The new tcpListenerPort value
+     */
+    void setTcpListenerPort( int val );
+
+    /**
+     * Gets the tcpListenerPort attribute of the ILateralCacheAttributes object
+     * <p>
+     * @return The tcpListenerPort value
+     */
+    int getTcpListenerPort();
+
+    /**
+     * Sets the tcpListenerHost attribute of the ILateralCacheAttributes object
+     * <p>
+     * @param val
+     *            The new tcpListenerHost value
+     */
+    void setTcpListenerHost( String val );
+
+    /**
+     * Gets the tcpListenerHost attribute of the ILateralCacheAttributes object
+     * <p>
+     * @return The tcpListenerHost value
+     */
+    String getTcpListenerHost();
+
+    /**
+     * Can setup UDP Discovery. This only works for TCp laterals right now. It
+     * allows TCP laterals to find each other by broadcasting to a multicast
+     * port.
+     * <p>
+     * @param udpDiscoveryEnabled
+     *            The udpDiscoveryEnabled to set.
+     */
+    void setUdpDiscoveryEnabled( boolean udpDiscoveryEnabled );
+
+    /**
+     * Whether or not TCP laterals can try to find each other by multicast
+     * communication.
+     * <p>
+     * @return Returns the udpDiscoveryEnabled.
+     */
+    boolean isUdpDiscoveryEnabled();
+
+    /**
+     * The port to use if UDPDiscovery is enabled.
+     * <p>
+     * @return Returns the udpDiscoveryPort.
+     */
+    int getUdpDiscoveryPort();
+
+    /**
+     * Sets the port to use if UDPDiscovery is enabled.
+     * <p>
+     * @param udpDiscoveryPort
+     *            The udpDiscoveryPort to set.
+     */
+    void setUdpDiscoveryPort( int udpDiscoveryPort );
+
+    /**
+     * The address to broadcast to if UDPDiscovery is enabled.
+     * <p>
+     * @return Returns the udpDiscoveryAddr.
+     */
+    String getUdpDiscoveryAddr();
+
+    /**
+     * Sets the address to broadcast to if UDPDiscovery is enabled.
+     * <p>
+     * @param udpDiscoveryAddr
+     *            The udpDiscoveryAddr to set.
+     */
+    void setUdpDiscoveryAddr( String udpDiscoveryAddr );
+
+    /**
+     * Is the lateral allowed to try and get from other laterals.
+     * <p>
+     * This replaces the old putOnlyMode
+     * <p>
+     * @param allowGet
+     */
+    void setAllowGet( boolean allowGet );
+
+    /**
+     * Is the lateral allowed to try and get from other laterals.
+     * <p>
+     * @return true if the lateral will try to get
+     */
+    boolean isAllowGet();
+
+    /**
+     * Is the lateral allowed to put objects to other laterals.
+     * <p>
+     * @param allowPut
+     */
+    void setAllowPut( boolean allowPut );
+
+    /**
+     * Is the lateral allowed to put objects to other laterals.
+     * <p>
+     * @return true if puts are allowed
+     */
+    boolean isAllowPut();
+
+    /**
+     * Should the client send a remove command rather than a put when update is
+     * called. This is a client option, not a receiver option. This allows you
+     * to prevent the lateral from serializing objects.
+     * <p>
+     * @param issueRemoveOnPut
+     */
+    void setIssueRemoveOnPut( boolean issueRemoveOnPut );
+
+    /**
+     * Should the client send a remove command rather than a put when update is
+     * called. This is a client option, not a receiver option. This allows you
+     * to prevent the lateral from serializing objects.
+     * <p>
+     * @return true if updates will result in a remove command being sent.
+     */
+    boolean isIssueRemoveOnPut();
+
+    /**
+     * Should the receiver try to match hashcodes. If true, the receiver will
+     * see if the client supplied a hashcode. If it did, then it will try to get
+     * the item locally. If the item exists, then it will compare the hashcode.
+     * if they are the same, it will not remove. This isn't perfect since
+     * different objects can have the same hashcode, but it is unlikely of
+     * objects of the same type.
+     * <p>
+     * @return boolean
+     */
+    boolean isFilterRemoveByHashCode();
+
+    /**
+     * Should the receiver try to match hashcodes. If true, the receiver will
+     * see if the client supplied a hashcode. If it did, then it will try to get
+     * the item locally. If the item exists, then it will compare the hashcode.
+     * if they are the same, it will not remove. This isn't perfect since
+     * different objects can have the same hashcode, but it is unlikely of
+     * objects of the same type.
+     * <p>
+     * @param filter
+     */
+    void setFilterRemoveByHashCode( boolean filter );
+
+    /**
+     * @param socketTimeOut the socketTimeOut to set
+     */
+    void setSocketTimeOut( int socketTimeOut );
+
+    /**
+     * @return the socketTimeOut
+     */
+    int getSocketTimeOut();
+
+    /**
+     * @param openTimeOut the openTimeOut to set
+     */
+    void setOpenTimeOut( int openTimeOut );
+
+    /**
+     * @return the openTimeOut
+     */
+    int getOpenTimeOut();
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/package.html b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/package.html
similarity index 100%
rename from commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/package.html
rename to commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/package.html
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/AbstractRemoteAuxiliaryCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/AbstractRemoteAuxiliaryCache.java
new file mode 100644
index 0000000..66009a6
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/AbstractRemoteAuxiliaryCache.java
@@ -0,0 +1,653 @@
+package org.apache.commons.jcs3.auxiliary.remote;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCacheEventLogging;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheClient;
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheListener;
+import org.apache.commons.jcs3.auxiliary.remote.server.behavior.RemoteType;
+import org.apache.commons.jcs3.engine.CacheStatus;
+import org.apache.commons.jcs3.engine.ZombieCacheServiceNonLocal;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheElementSerialized;
+import org.apache.commons.jcs3.engine.behavior.ICacheServiceNonLocal;
+import org.apache.commons.jcs3.engine.behavior.IZombie;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+import org.apache.commons.jcs3.engine.stats.StatElement;
+import org.apache.commons.jcs3.engine.stats.Stats;
+import org.apache.commons.jcs3.engine.stats.behavior.IStatElement;
+import org.apache.commons.jcs3.engine.stats.behavior.IStats;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.serialization.SerializationConversionUtil;
+import org.apache.commons.jcs3.utils.threadpool.ThreadPoolManager;
+
+/** Abstract base for remote caches. I'm trying to break out and reuse common functionality. */
+public abstract class AbstractRemoteAuxiliaryCache<K, V>
+    extends AbstractAuxiliaryCacheEventLogging<K, V>
+    implements IRemoteCacheClient<K, V>
+{
+    /** The logger. */
+    private static final Log log = LogManager.getLog( AbstractRemoteAuxiliaryCache.class );
+
+    /**
+     * This does the work. In an RMI instances, it will be a remote reference. In an http remote
+     * cache it will be an http client. In zombie mode it is replaced with a balking facade.
+     */
+    private ICacheServiceNonLocal<K, V> remoteCacheService;
+
+    /** The cacheName */
+    protected final String cacheName;
+
+    /** The listener. This can be null. */
+    private IRemoteCacheListener<K, V> remoteCacheListener;
+
+    /** The configuration values. TODO, we'll need a base here. */
+    private IRemoteCacheAttributes remoteCacheAttributes;
+
+    /** A thread pool for gets if configured. */
+    private ExecutorService pool = null;
+
+    /** Should we get asynchronously using a pool. */
+    private boolean usePoolForGet = false;
+
+    /**
+     * Creates the base.
+     * <p>
+     * @param cattr
+     * @param remote
+     * @param listener
+     */
+    public AbstractRemoteAuxiliaryCache( IRemoteCacheAttributes cattr, ICacheServiceNonLocal<K, V> remote,
+                                         IRemoteCacheListener<K, V> listener )
+    {
+        this.setRemoteCacheAttributes( cattr );
+        this.cacheName = cattr.getCacheName();
+        this.setRemoteCacheService( remote );
+        this.setRemoteCacheListener( listener );
+
+        if ( log.isDebugEnabled() )
+        {
+            log.debug( "Construct> cacheName={0}", () -> cattr.getCacheName() );
+            log.debug( "irca = {0}", () -> getRemoteCacheAttributes() );
+            log.debug( "remote = {0}", remote );
+            log.debug( "listener = {0}", listener );
+        }
+
+        // use a pool if it is greater than 0
+        log.debug( "GetTimeoutMillis() = {0}",
+                () -> getRemoteCacheAttributes().getGetTimeoutMillis() );
+
+        if ( getRemoteCacheAttributes().getGetTimeoutMillis() > 0 )
+        {
+            pool = ThreadPoolManager.getInstance().getExecutorService( getRemoteCacheAttributes().getThreadPoolName() );
+            log.debug( "Thread Pool = {0}", pool );
+            usePoolForGet = true;
+        }
+    }
+
+    /**
+     * Synchronously dispose the remote cache; if failed, replace the remote handle with a zombie.
+     * <p>
+     * @throws IOException
+     */
+    @Override
+    protected void processDispose()
+        throws IOException
+    {
+        log.info( "Disposing of remote cache." );
+        try
+        {
+            if ( getRemoteCacheListener() != null )
+            {
+                getRemoteCacheListener().dispose();
+            }
+        }
+        catch ( IOException ex )
+        {
+            log.error( "Couldn't dispose", ex );
+            handleException( ex, "Failed to dispose [" + cacheName + "]", ICacheEventLogger.DISPOSE_EVENT );
+        }
+    }
+
+    /**
+     * Synchronously get from the remote cache; if failed, replace the remote handle with a zombie.
+     * <p>
+     * Use threadpool to timeout if a value is set for GetTimeoutMillis
+     * <p>
+     * If we are a cluster client, we need to leave the Element in its serialized form. Cluster
+     * clients cannot deserialize objects. Cluster clients get ICacheElementSerialized objects from
+     * other remote servers.
+     * <p>
+     * @param key
+     * @return ICacheElement, a wrapper around the key, value, and attributes
+     * @throws IOException
+     */
+    @Override
+    protected ICacheElement<K, V> processGet( K key )
+        throws IOException
+    {
+        ICacheElement<K, V> retVal = null;
+        try
+        {
+            if ( usePoolForGet )
+            {
+                retVal = getUsingPool( key );
+            }
+            else
+            {
+                retVal = getRemoteCacheService().get( cacheName, key, getListenerId() );
+            }
+
+            // Eventually the instance of will not be necessary.
+            if ( retVal instanceof ICacheElementSerialized )
+            {
+                // Never try to deserialize if you are a cluster client. Cluster
+                // clients are merely intra-remote cache communicators. Remote caches are assumed
+                // to have no ability to deserialize the objects.
+                if ( this.getRemoteCacheAttributes().getRemoteType() != RemoteType.CLUSTER )
+                {
+                    retVal = SerializationConversionUtil.getDeSerializedCacheElement( (ICacheElementSerialized<K, V>) retVal,
+                            super.getElementSerializer() );
+                }
+            }
+        }
+        catch ( Exception ex )
+        {
+            handleException( ex, "Failed to get [" + key + "] from [" + cacheName + "]", ICacheEventLogger.GET_EVENT );
+        }
+        return retVal;
+    }
+
+    /**
+     * This allows gets to timeout in case of remote server machine shutdown.
+     * <p>
+     * @param key
+     * @return ICacheElement
+     * @throws IOException
+     */
+    public ICacheElement<K, V> getUsingPool( final K key )
+        throws IOException
+    {
+        int timeout = getRemoteCacheAttributes().getGetTimeoutMillis();
+
+        try
+        {
+            Callable<ICacheElement<K, V>> command = () -> getRemoteCacheService().get( cacheName, key, getListenerId() );
+
+            // execute using the pool
+            Future<ICacheElement<K, V>> future = pool.submit(command);
+
+            // used timed get in order to timeout
+            ICacheElement<K, V> ice = future.get(timeout, TimeUnit.MILLISECONDS);
+
+            if ( ice == null )
+            {
+                log.debug( "nothing found in remote cache" );
+            }
+            else
+            {
+                log.debug( "found item in remote cache" );
+            }
+            return ice;
+        }
+        catch ( TimeoutException te )
+        {
+            log.warn( "TimeoutException, Get Request timed out after {0}", timeout );
+            throw new IOException( "Get Request timed out after " + timeout );
+        }
+        catch ( InterruptedException ex )
+        {
+            log.warn( "InterruptedException, Get Request timed out after {0}", timeout );
+            throw new IOException( "Get Request timed out after " + timeout );
+        }
+        catch (ExecutionException ex)
+        {
+            // assume that this is an IOException thrown by the callable.
+            log.error( "ExecutionException, Assuming an IO exception thrown in the background.", ex );
+            throw new IOException( "Get Request timed out after " + timeout );
+        }
+    }
+
+    /**
+     * Calls get matching on the server. Each entry in the result is unwrapped.
+     * <p>
+     * @param pattern
+     * @return Map
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> processGetMatching( String pattern )
+        throws IOException
+    {
+        Map<K, ICacheElement<K, V>> results = new HashMap<>();
+        try
+        {
+            Map<K, ICacheElement<K, V>> rawResults = getRemoteCacheService().getMatching( cacheName, pattern, getListenerId() );
+
+            // Eventually the instance of will not be necessary.
+            if ( rawResults != null )
+            {
+                for (Map.Entry<K, ICacheElement<K, V>> entry : rawResults.entrySet())
+                {
+                    ICacheElement<K, V> unwrappedResult = null;
+                    if ( entry.getValue() instanceof ICacheElementSerialized )
+                    {
+                        // Never try to deserialize if you are a cluster client. Cluster
+                        // clients are merely intra-remote cache communicators. Remote caches are assumed
+                        // to have no ability to deserialize the objects.
+                        if ( this.getRemoteCacheAttributes().getRemoteType() != RemoteType.CLUSTER )
+                        {
+                            unwrappedResult = SerializationConversionUtil
+                                .getDeSerializedCacheElement( (ICacheElementSerialized<K, V>) entry.getValue(),
+                                        super.getElementSerializer() );
+                        }
+                    }
+                    else
+                    {
+                        unwrappedResult = entry.getValue();
+                    }
+                    results.put( entry.getKey(), unwrappedResult );
+                }
+            }
+        }
+        catch ( Exception ex )
+        {
+            handleException( ex, "Failed to getMatching [" + pattern + "] from [" + cacheName + "]",
+                             ICacheEventLogger.GET_EVENT );
+        }
+        return results;
+    }
+
+    /**
+     * Synchronously remove from the remote cache; if failed, replace the remote handle with a
+     * zombie.
+     * <p>
+     * @param key
+     * @return boolean, whether or not the item was removed
+     * @throws IOException
+     */
+    @Override
+    protected boolean processRemove( K key )
+        throws IOException
+    {
+        if ( !this.getRemoteCacheAttributes().getGetOnly() )
+        {
+            log.debug( "remove> key={0}", key );
+            try
+            {
+                getRemoteCacheService().remove( cacheName, key, getListenerId() );
+            }
+            catch ( Exception ex )
+            {
+                handleException( ex, "Failed to remove " + key + " from " + cacheName, ICacheEventLogger.REMOVE_EVENT );
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Synchronously removeAll from the remote cache; if failed, replace the remote handle with a
+     * zombie.
+     * <p>
+     * @throws IOException
+     */
+    @Override
+    protected void processRemoveAll()
+        throws IOException
+    {
+        if ( !this.getRemoteCacheAttributes().getGetOnly() )
+        {
+            try
+            {
+                getRemoteCacheService().removeAll( cacheName, getListenerId() );
+            }
+            catch ( Exception ex )
+            {
+                handleException( ex, "Failed to remove all from " + cacheName, ICacheEventLogger.REMOVEALL_EVENT );
+            }
+        }
+    }
+
+    /**
+     * Serializes the object and then calls update on the remote server with the byte array. The
+     * byte array is wrapped in a ICacheElementSerialized. This allows the remote server to operate
+     * without any knowledge of caches classes.
+     * <p>
+     * @param ce
+     * @throws IOException
+     */
+    @Override
+    protected void processUpdate( ICacheElement<K, V> ce )
+        throws IOException
+    {
+        if ( !getRemoteCacheAttributes().getGetOnly() )
+        {
+            ICacheElementSerialized<K, V> serialized = null;
+            try
+            {
+                log.debug( "sending item to remote server" );
+
+                // convert so we don't have to know about the object on the
+                // other end.
+                serialized = SerializationConversionUtil.getSerializedCacheElement( ce, super.getElementSerializer() );
+
+                remoteCacheService.update( serialized, getListenerId() );
+            }
+            catch ( NullPointerException npe )
+            {
+                log.error( "npe for ce = {0} ce.attr = {1}", ce, ce.getElementAttributes(), npe );
+            }
+            catch ( Exception ex )
+            {
+                // event queue will wait and retry
+                handleException( ex, "Failed to put [" + ce.getKey() + "] to " + ce.getCacheName(),
+                                 ICacheEventLogger.UPDATE_EVENT );
+            }
+        }
+        else
+        {
+            log.debug( "get only mode, not sending to remote server" );
+        }
+    }
+
+    /**
+     * Return the keys in this cache.
+     * <p>
+     * @see org.apache.commons.jcs3.auxiliary.AuxiliaryCache#getKeySet()
+     */
+    @Override
+    public Set<K> getKeySet()
+        throws IOException
+    {
+        return getRemoteCacheService().getKeySet(cacheName);
+    }
+
+    /**
+     * Allows other member of this package to access the listener. This is mainly needed for
+     * deregistering a listener.
+     * <p>
+     * @return IRemoteCacheListener, the listener for this remote server
+     */
+    @Override
+    public IRemoteCacheListener<K, V> getListener()
+    {
+        return getRemoteCacheListener();
+    }
+
+    /**
+     * let the remote cache set a listener_id. Since there is only one listener for all the regions
+     * and every region gets registered? the id shouldn't be set if it isn't zero. If it is we
+     * assume that it is a reconnect.
+     * <p>
+     * @param id The new listenerId value
+     */
+    public void setListenerId( long id )
+    {
+        if ( getRemoteCacheListener() != null )
+        {
+            try
+            {
+                getRemoteCacheListener().setListenerId( id );
+
+                log.debug( "set listenerId = {0}", id );
+            }
+            catch ( Exception e )
+            {
+                log.error( "Problem setting listenerId", e );
+            }
+        }
+    }
+
+    /**
+     * Gets the listenerId attribute of the RemoteCacheListener object
+     * <p>
+     * @return The listenerId value
+     */
+    @Override
+    public long getListenerId()
+    {
+        if ( getRemoteCacheListener() != null )
+        {
+            try
+            {
+                log.debug( "get listenerId = {0}", getRemoteCacheListener().getListenerId() );
+                return getRemoteCacheListener().getListenerId();
+            }
+            catch ( IOException e )
+            {
+                log.error( "Problem getting listenerId", e );
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Returns the current cache size.
+     * @return The size value
+     */
+    @Override
+    public int getSize()
+    {
+        return 0;
+    }
+
+    /**
+     * Custom exception handling some children.  This should be used to initiate failover.
+     * <p>
+     * @param ex
+     * @param msg
+     * @param eventName
+     * @throws IOException
+     */
+    protected abstract void handleException( Exception ex, String msg, String eventName )
+        throws IOException;
+
+    /**
+     * Gets the stats attribute of the RemoteCache object.
+     * <p>
+     * @return The stats value
+     */
+    @Override
+    public String getStats()
+    {
+        return getStatistics().toString();
+    }
+
+    /**
+     * @return IStats object
+     */
+    @Override
+    public IStats getStatistics()
+    {
+        IStats stats = new Stats();
+        stats.setTypeName( "AbstractRemoteAuxiliaryCache" );
+
+        ArrayList<IStatElement<?>> elems = new ArrayList<>();
+
+        elems.add(new StatElement<>( "Remote Type", this.getRemoteCacheAttributes().getRemoteTypeName() ) );
+
+//      if ( this.getRemoteCacheAttributes().getRemoteType() == RemoteType.CLUSTER )
+//      {
+//          // something cluster specific
+//      }
+
+        elems.add(new StatElement<>( "UsePoolForGet", Boolean.valueOf(usePoolForGet) ) );
+
+        if ( pool != null )
+        {
+            elems.add(new StatElement<>( "Pool", pool ) );
+        }
+
+        if ( getRemoteCacheService() instanceof ZombieCacheServiceNonLocal )
+        {
+            elems.add(new StatElement<>( "Zombie Queue Size",
+                    Integer.valueOf(( (ZombieCacheServiceNonLocal<K, V>) getRemoteCacheService() ).getQueueSize()) ) );
+        }
+
+        stats.setStatElements( elems );
+
+        return stats;
+    }
+
+    /**
+     * Returns the cache status. An error status indicates the remote connection is not available.
+     * <p>
+     * @return The status value
+     */
+    @Override
+    public CacheStatus getStatus()
+    {
+        return getRemoteCacheService() instanceof IZombie ? CacheStatus.ERROR : CacheStatus.ALIVE;
+    }
+
+    /**
+     * Replaces the current remote cache service handle with the given handle. If the current remote
+     * is a Zombie, then it propagates any events that are queued to the restored service.
+     * <p>
+     * @param restoredRemote ICacheServiceNonLocal -- the remote server or proxy to the remote server
+     */
+    @Override
+    public void fixCache( ICacheServiceNonLocal<?, ?> restoredRemote )
+    {
+        @SuppressWarnings("unchecked") // Don't know how to do this properly
+        ICacheServiceNonLocal<K, V> remote = (ICacheServiceNonLocal<K, V>)restoredRemote;
+        ICacheServiceNonLocal<K, V> prevRemote = getRemoteCacheService();
+        if ( prevRemote instanceof ZombieCacheServiceNonLocal )
+        {
+            ZombieCacheServiceNonLocal<K, V> zombie = (ZombieCacheServiceNonLocal<K, V>) prevRemote;
+            setRemoteCacheService( remote );
+            try
+            {
+                zombie.propagateEvents( remote );
+            }
+            catch ( Exception e )
+            {
+                try
+                {
+                    handleException( e, "Problem propagating events from Zombie Queue to new Remote Service.",
+                                     "fixCache" );
+                }
+                catch ( IOException e1 )
+                {
+                    // swallow, since this is just expected kick back.  Handle always throws
+                }
+            }
+        }
+        else
+        {
+            setRemoteCacheService( remote );
+        }
+    }
+
+
+    /**
+     * Gets the cacheType attribute of the RemoteCache object
+     * @return The cacheType value
+     */
+    @Override
+    public CacheType getCacheType()
+    {
+        return CacheType.REMOTE_CACHE;
+    }
+
+    /**
+     * Gets the cacheName attribute of the RemoteCache object.
+     * <p>
+     * @return The cacheName value
+     */
+    @Override
+    public String getCacheName()
+    {
+        return cacheName;
+    }
+
+    /**
+     * @param remote the remote to set
+     */
+    protected void setRemoteCacheService( ICacheServiceNonLocal<K, V> remote )
+    {
+        this.remoteCacheService = remote;
+    }
+
+    /**
+     * @return the remote
+     */
+    protected ICacheServiceNonLocal<K, V> getRemoteCacheService()
+    {
+        return remoteCacheService;
+    }
+
+    /**
+     * @return Returns the AuxiliaryCacheAttributes.
+     */
+    @Override
+    public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
+    {
+        return getRemoteCacheAttributes();
+    }
+
+    /**
+     * @param remoteCacheAttributes the remoteCacheAttributes to set
+     */
+    protected void setRemoteCacheAttributes( IRemoteCacheAttributes remoteCacheAttributes )
+    {
+        this.remoteCacheAttributes = remoteCacheAttributes;
+    }
+
+    /**
+     * @return the remoteCacheAttributes
+     */
+    protected IRemoteCacheAttributes getRemoteCacheAttributes()
+    {
+        return remoteCacheAttributes;
+    }
+
+    /**
+     * @param remoteCacheListener the remoteCacheListener to set
+     */
+    protected void setRemoteCacheListener( IRemoteCacheListener<K, V> remoteCacheListener )
+    {
+        this.remoteCacheListener = remoteCacheListener;
+    }
+
+    /**
+     * @return the remoteCacheListener
+     */
+    protected IRemoteCacheListener<K, V> getRemoteCacheListener()
+    {
+        return remoteCacheListener;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/AbstractRemoteCacheListener.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/AbstractRemoteCacheListener.java
new file mode 100644
index 0000000..451c71b
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/AbstractRemoteCacheListener.java
@@ -0,0 +1,266 @@
+package org.apache.commons.jcs3.auxiliary.remote;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.net.UnknownHostException;
+
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheListener;
+import org.apache.commons.jcs3.auxiliary.remote.server.behavior.RemoteType;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheElementSerialized;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.net.HostNameUtil;
+import org.apache.commons.jcs3.utils.serialization.SerializationConversionUtil;
+
+/** Shared listener base. */
+public abstract class AbstractRemoteCacheListener<K, V>
+    implements IRemoteCacheListener<K, V>
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog( AbstractRemoteCacheListener.class );
+
+    /** The cached name of the local host. The remote server gets this for logging purposes. */
+    private static String localHostName = null;
+
+    /**
+     * The cache manager used to put items in different regions. This is set lazily and should not
+     * be sent to the remote server.
+     */
+    private final ICompositeCacheManager cacheMgr;
+
+    /** The remote cache configuration object. */
+    private final IRemoteCacheAttributes irca;
+
+    /** This is set by the remote cache server. */
+    private long listenerId = 0;
+
+    /** Custom serializer. */
+    private final IElementSerializer elementSerializer;
+
+    /**
+     * Only need one since it does work for all regions, just reference by multiple region names.
+     * <p>
+     * The constructor exports this object, making it available to receive incoming calls. The
+     * callback port is anonymous unless a local port value was specified in the configuration.
+     * <p>
+     * @param irca cache configuration
+     * @param cacheMgr the cache hub
+     * @param elementSerializer a custom serializer
+     */
+    public AbstractRemoteCacheListener( IRemoteCacheAttributes irca, ICompositeCacheManager cacheMgr, IElementSerializer elementSerializer )
+    {
+        this.irca = irca;
+        this.cacheMgr = cacheMgr;
+        this.elementSerializer = elementSerializer;
+    }
+
+    /**
+     * Let the remote cache set a listener_id. Since there is only one listener for all the regions
+     * and every region gets registered? the id shouldn't be set if it isn't zero. If it is we
+     * assume that it is a reconnect.
+     * <p>
+     * @param id The new listenerId value
+     * @throws IOException
+     */
+    @Override
+    public void setListenerId( long id )
+        throws IOException
+    {
+        listenerId = id;
+        log.info( "set listenerId = [{0}]", id );
+    }
+
+    /**
+     * Gets the listenerId attribute of the RemoteCacheListener object. This is stored in the
+     * object. The RemoteCache object contains a reference to the listener and get the id this way.
+     * <p>
+     * @return The listenerId value
+     * @throws IOException
+     */
+    @Override
+    public long getListenerId()
+        throws IOException
+    {
+        log.debug( "get listenerId = [{0}]", listenerId );
+        return listenerId;
+
+    }
+
+    /**
+     * Gets the remoteType attribute of the RemoteCacheListener object
+     * <p>
+     * @return The remoteType value
+     * @throws IOException
+     */
+    @Override
+    public RemoteType getRemoteType()
+        throws IOException
+    {
+        log.debug( "getRemoteType = [{0}]", () -> irca.getRemoteType() );
+        return irca.getRemoteType();
+    }
+
+    /**
+     * If this is configured to remove on put, then remove the element since it has been updated
+     * elsewhere. cd should be incomplete for faster transmission. We don't want to pass data only
+     * invalidation. The next time it is used the local cache will get the new version from the
+     * remote store.
+     * <p>
+     * If remove on put is not configured, then update the item.
+     * @param cb
+     * @throws IOException
+     */
+    @Override
+    public void handlePut( ICacheElement<K, V> cb )
+        throws IOException
+    {
+        if ( irca.getRemoveUponRemotePut() )
+        {
+            log.debug( "PUTTING ELEMENT FROM REMOTE, (  invalidating ) " );
+            handleRemove( cb.getCacheName(), cb.getKey() );
+        }
+        else
+        {
+            log.debug( "PUTTING ELEMENT FROM REMOTE, ( updating ) " );
+            log.debug( "cb = {0}", cb );
+
+            // Eventually the instance of will not be necessary.
+            if ( cb instanceof ICacheElementSerialized )
+            {
+                log.debug( "Object needs to be deserialized." );
+                try
+                {
+                    cb = SerializationConversionUtil.getDeSerializedCacheElement(
+                            (ICacheElementSerialized<K, V>) cb, this.elementSerializer );
+                    log.debug( "Deserialized result = {0}", cb );
+                }
+                catch ( IOException e )
+                {
+                    throw e;
+                }
+                catch ( ClassNotFoundException e )
+                {
+                    log.error( "Received a serialized version of a class that we don't know about.", e );
+                }
+            }
+
+            getCacheManager().<K, V>getCache( cb.getCacheName() ).localUpdate( cb );
+        }
+    }
+
+    /**
+     * Calls localRemove on the CompositeCache.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @throws IOException
+     */
+    @Override
+    public void handleRemove( String cacheName, K key )
+        throws IOException
+    {
+        log.debug( "handleRemove> cacheName={0}, key={1}", cacheName, key );
+
+        getCacheManager().<K, V>getCache( cacheName ).localRemove( key );
+    }
+
+    /**
+     * Calls localRemoveAll on the CompositeCache.
+     * <p>
+     * @param cacheName
+     * @throws IOException
+     */
+    @Override
+    public void handleRemoveAll( String cacheName )
+        throws IOException
+    {
+        log.debug( "handleRemoveAll> cacheName={0}", cacheName );
+
+        getCacheManager().<K, V>getCache( cacheName ).localRemoveAll();
+    }
+
+    /**
+     * @param cacheName
+     * @throws IOException
+     */
+    @Override
+    public void handleDispose( String cacheName )
+        throws IOException
+    {
+        log.debug( "handleDispose> cacheName={0}", cacheName );
+        // TODO consider what to do here, we really don't want to
+        // dispose, we just want to disconnect.
+        // just allow the cache to go into error recovery mode.
+        // getCacheManager().freeCache( cacheName, true );
+    }
+
+    /**
+     * Gets the cacheManager attribute of the RemoteCacheListener object. This is one of the few
+     * places that force the cache to be a singleton.
+     */
+    protected ICompositeCacheManager getCacheManager()
+    {
+        return cacheMgr;
+    }
+
+    /**
+     * This is for debugging. It allows the remote server to log the address of clients.
+     * <p>
+     * @return String
+     * @throws IOException
+     */
+    @Override
+    public synchronized String getLocalHostAddress()
+        throws IOException
+    {
+        if ( localHostName == null )
+        {
+            try
+            {
+                localHostName = HostNameUtil.getLocalHostAddress();
+            }
+            catch ( UnknownHostException uhe )
+            {
+                localHostName = "unknown";
+            }
+        }
+        return localHostName;
+    }
+
+    /**
+     * For easier debugging.
+     * <p>
+     * @return Basic info on this listener.
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        buf.append( "\n AbstractRemoteCacheListener: " )
+           .append( "\n RemoteHost = ").append(irca.getRemoteLocation())
+           .append( "\n ListenerId = ").append(listenerId);
+        return buf.toString();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/AbstractRemoteCacheNoWaitFacade.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/AbstractRemoteCacheNoWaitFacade.java
new file mode 100644
index 0000000..cdc2baf
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/AbstractRemoteCacheNoWaitFacade.java
@@ -0,0 +1,434 @@
+package org.apache.commons.jcs3.auxiliary.remote;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCache;
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheAttributes;
+import org.apache.commons.jcs3.engine.CacheStatus;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+import org.apache.commons.jcs3.engine.stats.StatElement;
+import org.apache.commons.jcs3.engine.stats.Stats;
+import org.apache.commons.jcs3.engine.stats.behavior.IStatElement;
+import org.apache.commons.jcs3.engine.stats.behavior.IStats;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/** An abstract base for the No Wait Facade.  Different implementations will failover differently. */
+public abstract class AbstractRemoteCacheNoWaitFacade<K, V>
+    extends AbstractAuxiliaryCache<K, V>
+{
+    /** log instance */
+    private static final Log log = LogManager.getLog( AbstractRemoteCacheNoWaitFacade.class );
+
+    /** The connection to a remote server, or a zombie. */
+    protected List<RemoteCacheNoWait<K, V>> noWaits;
+
+    /** holds failover and cluster information */
+    private final IRemoteCacheAttributes remoteCacheAttributes;
+
+    /**
+     * Constructs with the given remote cache, and fires events to any listeners.
+     * <p>
+     * @param noWaits
+     * @param rca
+     * @param cacheEventLogger
+     * @param elementSerializer
+     */
+    public AbstractRemoteCacheNoWaitFacade( List<RemoteCacheNoWait<K,V>> noWaits, IRemoteCacheAttributes rca,
+                                    ICacheEventLogger cacheEventLogger, IElementSerializer elementSerializer )
+    {
+        log.debug( "CONSTRUCTING NO WAIT FACADE" );
+        this.remoteCacheAttributes = rca;
+        setCacheEventLogger( cacheEventLogger );
+        setElementSerializer( elementSerializer );
+        this.noWaits = new ArrayList<>(noWaits);
+        for (RemoteCacheNoWait<K,V> nw : this.noWaits)
+        {
+            // FIXME: This cast is very brave. Remove this.
+            ((RemoteCache<K, V>)nw.getRemoteCache()).setFacade(this);
+        }
+    }
+
+    /**
+     * Put an element in the cache.
+     * <p>
+     * @param ce
+     * @throws IOException
+     */
+    @Override
+    public void update( ICacheElement<K, V> ce )
+        throws IOException
+    {
+        log.debug( "updating through cache facade, noWaits.length = {0}",
+                () -> noWaits.size() );
+
+        for (RemoteCacheNoWait<K, V> nw : noWaits)
+        {
+            try
+            {
+                nw.update( ce );
+                // an initial move into a zombie will lock this to primary
+                // recovery. will not discover other servers until primary
+                // reconnect
+                // and subsequent error
+            }
+            catch ( IOException ex )
+            {
+                String message = "Problem updating no wait. Will initiate failover if the noWait is in error.";
+                log.error( message, ex );
+
+                if ( getCacheEventLogger() != null )
+                {
+                    getCacheEventLogger().logError( "RemoteCacheNoWaitFacade",
+                                                    ICacheEventLogger.UPDATE_EVENT,
+                                                    message + ":" + ex.getMessage() + " REGION: " + ce.getCacheName()
+                                                        + " ELEMENT: " + ce );
+                }
+
+                // can handle failover here? Is it safe to try the others?
+                // check to see it the noWait is now a zombie
+                // if it is a zombie, then move to the next in the failover list
+                // will need to keep them in order or a count
+                failover( nw );
+                // should start a failover thread
+                // should probably only failover if there is only one in the noWait
+                // list
+                // Should start a background thread to restore the original primary if we are in failover state.
+            }
+        }
+    }
+
+    /**
+     * Synchronously reads from the remote cache.
+     * <p>
+     * @param key
+     * @return Either an ICacheElement&lt;K, V&gt; or null if it is not found.
+     */
+    @Override
+    public ICacheElement<K, V> get( K key )
+    {
+        for (RemoteCacheNoWait<K, V> nw : noWaits)
+        {
+            try
+            {
+                ICacheElement<K, V> obj = nw.get( key );
+                if ( obj != null )
+                {
+                    return obj;
+                }
+            }
+            catch ( IOException ex )
+            {
+                log.debug( "Failed to get." );
+                return null;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Synchronously read from the remote cache.
+     * <p>
+     * @param pattern
+     * @return map
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMatching( String pattern )
+        throws IOException
+    {
+        for (RemoteCacheNoWait<K, V> nw : noWaits)
+        {
+            try
+            {
+                return nw.getMatching( pattern );
+            }
+            catch ( IOException ex )
+            {
+                log.debug( "Failed to getMatching." );
+            }
+        }
+        return Collections.emptyMap();
+    }
+
+    /**
+     * Gets multiple items from the cache based on the given set of keys.
+     * <p>
+     * @param keys
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache for any of these keys
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMultiple( Set<K> keys )
+    {
+        if ( keys != null && !keys.isEmpty() )
+        {
+            for (RemoteCacheNoWait<K, V> nw : noWaits)
+            {
+                try
+                {
+                    return nw.getMultiple( keys );
+                }
+                catch ( IOException ex )
+                {
+                    log.debug( "Failed to get." );
+                }
+            }
+        }
+
+        return Collections.emptyMap();
+    }
+
+    /**
+     * Return the keys in this cache.
+     * <p>
+     * @see org.apache.commons.jcs3.auxiliary.AuxiliaryCache#getKeySet()
+     */
+    @Override
+    public Set<K> getKeySet() throws IOException
+    {
+        HashSet<K> allKeys = new HashSet<>();
+        for (RemoteCacheNoWait<K, V> nw : noWaits)
+        {
+            if ( nw != null )
+            {
+                Set<K> keys = nw.getKeySet();
+                if(keys != null)
+                {
+                    allKeys.addAll( keys );
+                }
+            }
+        }
+        return allKeys;
+    }
+
+    /**
+     * Adds a remove request to the remote cache.
+     * <p>
+     * @param key
+     * @return whether or not it was removed, right now it return false.
+     */
+    @Override
+    public boolean remove( K key )
+    {
+        try
+        {
+            for (RemoteCacheNoWait<K, V> nw : noWaits)
+            {
+                nw.remove( key );
+            }
+        }
+        catch ( IOException ex )
+        {
+            log.error( ex );
+        }
+        return false;
+    }
+
+    /**
+     * Adds a removeAll request to the remote cache.
+     */
+    @Override
+    public void removeAll()
+    {
+        try
+        {
+            for (RemoteCacheNoWait<K, V> nw : noWaits)
+            {
+                nw.removeAll();
+            }
+        }
+        catch ( IOException ex )
+        {
+            log.error( ex );
+        }
+    }
+
+    /** Adds a dispose request to the remote cache. */
+    @Override
+    public void dispose()
+    {
+        for (RemoteCacheNoWait<K, V> nw : noWaits)
+        {
+            nw.dispose();
+        }
+    }
+
+    /**
+     * No remote invocation.
+     * <p>
+     * @return The size value
+     */
+    @Override
+    public int getSize()
+    {
+        return 0;
+        // cache.getSize();
+    }
+
+    /**
+     * Gets the cacheType attribute of the RemoteCacheNoWaitFacade object.
+     * <p>
+     * @return The cacheType value
+     */
+    @Override
+    public CacheType getCacheType()
+    {
+        return CacheType.REMOTE_CACHE;
+    }
+
+    /**
+     * Gets the cacheName attribute of the RemoteCacheNoWaitFacade object.
+     * <p>
+     * @return The cacheName value
+     */
+    @Override
+    public String getCacheName()
+    {
+        return remoteCacheAttributes.getCacheName();
+    }
+
+    /**
+     * Gets the status attribute of the RemoteCacheNoWaitFacade object
+     * <p>
+     * Return ALIVE if any are alive.
+     * <p>
+     * @return The status value
+     */
+    @Override
+    public CacheStatus getStatus()
+    {
+        for (RemoteCacheNoWait<K, V> nw : noWaits)
+        {
+            if ( nw.getStatus() == CacheStatus.ALIVE )
+            {
+                return CacheStatus.ALIVE;
+            }
+        }
+
+        return CacheStatus.DISPOSED;
+    }
+
+    /**
+     * String form of some of the configuration information for the remote cache.
+     * <p>
+     * @return Some info for logging.
+     */
+    @Override
+    public String toString()
+    {
+        return "RemoteCacheNoWaitFacade: " + remoteCacheAttributes.getCacheName() + ", rca = " + remoteCacheAttributes;
+    }
+
+    /**
+     * Begin the failover process if this is a local cache. Clustered remote caches do not failover.
+     * <p>
+     * @param rcnw The no wait in error.
+     */
+    protected abstract void failover( RemoteCacheNoWait<K, V> rcnw );
+
+    /**
+     * Get the primary server from the list of failovers
+     *
+     * @return a no wait
+     */
+    public RemoteCacheNoWait<K, V> getPrimaryServer()
+    {
+        return noWaits.get(0);
+    }
+
+    /**
+     * restore the primary server in the list of failovers
+     *
+     */
+    public void restorePrimaryServer(RemoteCacheNoWait<K, V> rcnw)
+    {
+        noWaits.clear();
+        noWaits.add(rcnw);
+    }
+
+    /**
+     * @return Returns the AuxiliaryCacheAttributes.
+     */
+    @Override
+    public IRemoteCacheAttributes getAuxiliaryCacheAttributes()
+    {
+        return this.remoteCacheAttributes;
+    }
+
+    /**
+     * getStats
+     * @return String
+     */
+    @Override
+    public String getStats()
+    {
+        return getStatistics().toString();
+    }
+
+    /**
+     * @return statistics about the cache region
+     */
+    @Override
+    public IStats getStatistics()
+    {
+        IStats stats = new Stats();
+        stats.setTypeName( "Remote Cache No Wait Facade" );
+
+        ArrayList<IStatElement<?>> elems = new ArrayList<>();
+
+        if ( noWaits != null )
+        {
+            elems.add(new StatElement<>( "Number of No Waits", Integer.valueOf(noWaits.size()) ) );
+
+            for ( RemoteCacheNoWait<K, V> rcnw : noWaits )
+            {
+                // get the stats from the super too
+                IStats sStats = rcnw.getStatistics();
+                elems.addAll(sStats.getStatElements());
+            }
+        }
+
+        stats.setStatElements( elems );
+
+        return stats;
+    }
+
+    /**
+     * This typically returns end point info .
+     * <p>
+     * @return the name
+     */
+    @Override
+    public String getEventLoggingExtraInfo()
+    {
+        return "Remote Cache No Wait Facade";
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/CommonRemoteCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/CommonRemoteCacheAttributes.java
new file mode 100644
index 0000000..39d9de1
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/CommonRemoteCacheAttributes.java
@@ -0,0 +1,295 @@
+package org.apache.commons.jcs3.auxiliary.remote;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.remote.behavior.ICommonRemoteCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheConstants;
+import org.apache.commons.jcs3.auxiliary.remote.server.behavior.RemoteType;
+
+/**
+ * Attributes common to remote cache client and server.
+ */
+public class CommonRemoteCacheAttributes
+    extends AbstractAuxiliaryCacheAttributes
+    implements ICommonRemoteCacheAttributes
+{
+    /** Don't change */
+    private static final long serialVersionUID = -1555143736942374000L;
+
+    /** The service name */
+    private String remoteServiceName = IRemoteCacheConstants.REMOTE_CACHE_SERVICE_VAL;
+
+    /** server host and port */
+    private RemoteLocation location;
+
+    /** Cluster chain */
+    private String clusterServers = "";
+
+    /** THe type of remote cache, local or cluster */
+    private RemoteType remoteType = RemoteType.LOCAL;
+
+    /** Should we issue a local remove if we get a put from a remote server */
+    private boolean removeUponRemotePut = true;
+
+    /** Can we receive from or put to the remote. this probably shouldn't be used. Use receive. */
+    private boolean getOnly = false;
+
+    /** Should we put and get from the clusters. */
+    private boolean localClusterConsistency = false;
+
+    /** read and connect timeout */
+    private int rmiSocketFactoryTimeoutMillis = DEFAULT_RMI_SOCKET_FACTORY_TIMEOUT_MILLIS;
+
+    /** Default constructor for the RemoteCacheAttributes object */
+    public CommonRemoteCacheAttributes()
+    {
+        super();
+    }
+
+    /**
+     * Gets the remoteTypeName attribute of the RemoteCacheAttributes object.
+     * <p>
+     * @return The remoteTypeName value
+     */
+    @Override
+    public String getRemoteTypeName()
+    {
+        return remoteType != null ? remoteType.toString() : RemoteType.LOCAL.toString();
+    }
+
+    /**
+     * Sets the remoteTypeName attribute of the RemoteCacheAttributes object.
+     * <p>
+     * @param s The new remoteTypeName value
+     */
+    @Override
+    public void setRemoteTypeName( String s )
+    {
+        RemoteType rt = RemoteType.valueOf(s);
+        if (rt != null)
+        {
+            this.remoteType = rt;
+        }
+    }
+
+    /**
+     * Gets the remoteType attribute of the RemoteCacheAttributes object.
+     * <p>
+     * @return The remoteType value
+     */
+    @Override
+    public RemoteType getRemoteType()
+    {
+        return remoteType;
+    }
+
+    /**
+     * Sets the remoteType attribute of the RemoteCacheAttributes object.
+     * <p>
+     * @param p The new remoteType value
+     */
+    @Override
+    public void setRemoteType( RemoteType p )
+    {
+        this.remoteType = p;
+    }
+
+    /**
+     * Gets the remoteServiceName attribute of the RemoteCacheAttributes object.
+     * <p>
+     * @return The remoteServiceName value
+     */
+    @Override
+    public String getRemoteServiceName()
+    {
+        return this.remoteServiceName;
+    }
+
+    /**
+     * Sets the remoteServiceName attribute of the RemoteCacheAttributes object.
+     * <p>
+     * @param s The new remoteServiceName value
+     */
+    @Override
+    public void setRemoteServiceName( String s )
+    {
+        this.remoteServiceName = s;
+    }
+
+    /**
+     * Sets the location attribute of the RemoteCacheAttributes object.
+     * <p>
+     * @param location The new location value
+     */
+    @Override
+    public void setRemoteLocation( RemoteLocation location )
+    {
+        this.location = location;
+    }
+
+    /**
+     * Sets the location attribute of the RemoteCacheAttributes object.
+     * <p>
+     * @param host The new remoteHost value
+     * @param port The new remotePort value
+     */
+    @Override
+    public void setRemoteLocation( String host, int port )
+    {
+        this.location = new RemoteLocation(host, port);
+    }
+
+    /**
+     * Gets the location attribute of the RemoteCacheAttributes object.
+     * <p>
+     * @return The remote location value
+     */
+    @Override
+    public RemoteLocation getRemoteLocation()
+    {
+        return this.location;
+    }
+
+    /**
+     * Gets the clusterServers attribute of the RemoteCacheAttributes object.
+     * <p>
+     * @return The clusterServers value
+     */
+    @Override
+    public String getClusterServers()
+    {
+        return this.clusterServers;
+    }
+
+    /**
+     * Sets the clusterServers attribute of the RemoteCacheAttributes object.
+     * <p>
+     * @param s The new clusterServers value
+     */
+    @Override
+    public void setClusterServers( String s )
+    {
+        this.clusterServers = s;
+    }
+
+    /**
+     * Gets the removeUponRemotePut attribute of the RemoteCacheAttributes object.
+     * <p>
+     * @return The removeUponRemotePut value
+     */
+    @Override
+    public boolean getRemoveUponRemotePut()
+    {
+        return this.removeUponRemotePut;
+    }
+
+    /**
+     * Sets the removeUponRemotePut attribute of the RemoteCacheAttributes object.
+     * <p>
+     * @param r The new removeUponRemotePut value
+     */
+    @Override
+    public void setRemoveUponRemotePut( boolean r )
+    {
+        this.removeUponRemotePut = r;
+    }
+
+    /**
+     * Gets the getOnly attribute of the RemoteCacheAttributes object.
+     * <p>
+     * @return The getOnly value
+     */
+    @Override
+    public boolean getGetOnly()
+    {
+        return this.getOnly;
+    }
+
+    /**
+     * Sets the getOnly attribute of the RemoteCacheAttributes object
+     * @param r The new getOnly value
+     */
+    @Override
+    public void setGetOnly( boolean r )
+    {
+        this.getOnly = r;
+    }
+
+    /**
+     * Should cluster updates be propagated to the locals.
+     * <p>
+     * @return The localClusterConsistency value
+     */
+    @Override
+    public boolean isLocalClusterConsistency()
+    {
+        return localClusterConsistency;
+    }
+
+    /**
+     * Should cluster updates be propagated to the locals.
+     * <p>
+     * @param r The new localClusterConsistency value
+     */
+    @Override
+    public void setLocalClusterConsistency( boolean r )
+    {
+        this.localClusterConsistency = r;
+    }
+
+    /**
+     * @param rmiSocketFactoryTimeoutMillis The rmiSocketFactoryTimeoutMillis to set.
+     */
+    @Override
+    public void setRmiSocketFactoryTimeoutMillis( int rmiSocketFactoryTimeoutMillis )
+    {
+        this.rmiSocketFactoryTimeoutMillis = rmiSocketFactoryTimeoutMillis;
+    }
+
+    /**
+     * @return Returns the rmiSocketFactoryTimeoutMillis.
+     */
+    @Override
+    public int getRmiSocketFactoryTimeoutMillis()
+    {
+        return rmiSocketFactoryTimeoutMillis;
+    }
+
+    /**
+     * @return String, all the important values that can be configured
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        buf.append( "\n RemoteCacheAttributes " );
+        if (this.location != null)
+        {
+            buf.append( "\n remoteHost = [" + this.location.getHost() + "]" );
+            buf.append( "\n remotePort = [" + this.location.getPort() + "]" );
+        }
+        buf.append( "\n cacheName = [" + getCacheName() + "]" );
+        buf.append( "\n remoteType = [" + remoteType + "]" );
+        buf.append( "\n removeUponRemotePut = [" + this.removeUponRemotePut + "]" );
+        buf.append( "\n getOnly = [" + getOnly + "]" );
+        return buf.toString();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCache.java
new file mode 100644
index 0000000..6a62adb
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCache.java
@@ -0,0 +1,209 @@
+package org.apache.commons.jcs3.auxiliary.remote;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheListener;
+import org.apache.commons.jcs3.auxiliary.remote.server.behavior.RemoteType;
+import org.apache.commons.jcs3.engine.ZombieCacheServiceNonLocal;
+import org.apache.commons.jcs3.engine.behavior.ICacheServiceNonLocal;
+import org.apache.commons.jcs3.engine.stats.StatElement;
+import org.apache.commons.jcs3.engine.stats.Stats;
+import org.apache.commons.jcs3.engine.stats.behavior.IStatElement;
+import org.apache.commons.jcs3.engine.stats.behavior.IStats;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * Client proxy for an RMI remote cache.
+ * <p>
+ * This handles gets, updates, and removes. It also initiates failover recovery when an error is
+ * encountered.
+ */
+public class RemoteCache<K, V>
+    extends AbstractRemoteAuxiliaryCache<K, V>
+{
+    /** The logger. */
+    private static final Log log = LogManager.getLog( RemoteCache.class );
+
+    /** for error notifications */
+    private final RemoteCacheMonitor monitor;
+
+    /** back link for failover initiation */
+    private AbstractRemoteCacheNoWaitFacade<K, V> facade;
+
+    /**
+     * Constructor for the RemoteCache object. This object communicates with a remote cache server.
+     * One of these exists for each region. This also holds a reference to a listener. The same
+     * listener is used for all regions for one remote server. Holding a reference to the listener
+     * allows this object to know the listener id assigned by the remote cache.
+     * <p>
+     * @param cattr the cache configuration
+     * @param remote the remote cache server handle
+     * @param listener a listener
+     * @param monitor the cache monitor
+     */
+    public RemoteCache( IRemoteCacheAttributes cattr,
+        ICacheServiceNonLocal<K, V> remote,
+        IRemoteCacheListener<K, V> listener,
+        RemoteCacheMonitor monitor )
+    {
+        super( cattr, remote, listener );
+        this.monitor = monitor;
+
+        RemoteUtils.configureGlobalCustomSocketFactory( getRemoteCacheAttributes().getRmiSocketFactoryTimeoutMillis() );
+    }
+
+    /**
+     * @return IStats object
+     */
+    @Override
+    public IStats getStatistics()
+    {
+        IStats stats = new Stats();
+        stats.setTypeName( "Remote Cache" );
+
+        ArrayList<IStatElement<?>> elems = new ArrayList<>();
+
+        elems.add(new StatElement<>( "Remote Host:Port", getIPAddressForService() ) );
+        elems.add(new StatElement<>( "Remote Type", this.getRemoteCacheAttributes().getRemoteTypeName() ) );
+
+//      if ( this.getRemoteCacheAttributes().getRemoteType() == RemoteType.CLUSTER )
+//      {
+//          // something cluster specific
+//      }
+
+        // get the stats from the super too
+        IStats sStats = super.getStatistics();
+        elems.addAll(sStats.getStatElements());
+
+        stats.setStatElements( elems );
+
+        return stats;
+    }
+
+    /**
+     * Set facade
+     *
+     * @param facade the facade to set
+     */
+    protected void setFacade(AbstractRemoteCacheNoWaitFacade<K, V> facade)
+    {
+        this.facade = facade;
+    }
+
+    /**
+     * Get facade
+     *
+     * @return the facade
+     */
+    protected AbstractRemoteCacheNoWaitFacade<K, V> getFacade()
+    {
+        return facade;
+    }
+
+    /**
+     * Handles exception by disabling the remote cache service before re-throwing the exception in
+     * the form of an IOException.
+     * <p>
+     * @param ex
+     * @param msg
+     * @param eventName
+     * @throws IOException
+     */
+    @Override
+    protected void handleException( Exception ex, String msg, String eventName )
+        throws IOException
+    {
+        String message = "Disabling remote cache due to error: " + msg;
+
+        logError( cacheName, "", message );
+        log.error( message, ex );
+
+        // we should not switch if the existing is a zombie.
+        if ( getRemoteCacheService() == null || !( getRemoteCacheService() instanceof ZombieCacheServiceNonLocal ) )
+        {
+            // TODO make configurable
+            setRemoteCacheService( new ZombieCacheServiceNonLocal<>( getRemoteCacheAttributes().getZombieQueueMaxSize() ) );
+        }
+        // may want to flush if region specifies
+        // Notify the cache monitor about the error, and kick off the recovery
+        // process.
+        monitor.notifyError();
+
+        log.debug( "Initiating failover, rcnwf = {0}", facade );
+
+        if ( facade != null && facade.getAuxiliaryCacheAttributes().getRemoteType() == RemoteType.LOCAL )
+        {
+            log.debug( "Found facade, calling failover" );
+            // may need to remove the noWait index here. It will be 0 if it is
+            // local since there is only 1 possible listener.
+            facade.failover( facade.getPrimaryServer() );
+        }
+
+        if ( ex instanceof IOException )
+        {
+            throw (IOException) ex;
+        }
+        throw new IOException( ex );
+    }
+
+    /**
+     * Debugging info.
+     * <p>
+     * @return basic info about the RemoteCache
+     */
+    @Override
+    public String toString()
+    {
+        return "RemoteCache: " + cacheName + " attributes = " + getRemoteCacheAttributes();
+    }
+
+    /**
+     * Gets the extra info for the event log.
+     * <p>
+     * @return disk location
+     */
+    @Override
+    public String getEventLoggingExtraInfo()
+    {
+        return getIPAddressForService();
+    }
+
+    /**
+     * IP address for the service, if one is stored.
+     * <p>
+     * Protected for testing.
+     * <p>
+     * @return String
+     */
+    protected String getIPAddressForService()
+    {
+        String ipAddress = "(null)";
+        if (this.getRemoteCacheAttributes().getRemoteLocation() != null)
+        {
+            ipAddress = this.getRemoteCacheAttributes().getRemoteLocation().toString();
+        }
+        return ipAddress;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheAttributes.java
new file mode 100644
index 0000000..ed5de40
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheAttributes.java
@@ -0,0 +1,263 @@
+package org.apache.commons.jcs3.auxiliary.remote;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.List;
+
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheAttributes;
+
+/**
+ * These objects are used to configure the remote cache client.
+ */
+public class RemoteCacheAttributes
+    extends CommonRemoteCacheAttributes
+    implements IRemoteCacheAttributes
+{
+    /** Don't change */
+    private static final long serialVersionUID = -1555143736942374000L;
+
+    /**
+     * Failover servers will be used by local caches one at a time. Listeners will be registered
+     * with all cluster servers. If we add a get from cluster attribute we will have the ability to
+     * chain clusters and have them get from each other.
+     */
+    private String failoverServers = "";
+
+    /** callback */
+    private int localPort = 0;
+
+    /** what failover server we are connected to. */
+    private int failoverIndex = 0;
+
+    /** List of failover server addresses */
+    private List<RemoteLocation> failovers;
+
+    /** default name is remote_cache_client */
+    private String threadPoolName = "remote_cache_client";
+
+    /** must be greater than 0 for a pool to be used. */
+    private int getTimeoutMillis = -1;
+
+    /**
+     * Can we receive from the server. You might have a 0 local store and keep everything on the
+     * remote. If so, you don't want to be notified of updates.
+     */
+    private boolean receive = DEFAULT_RECEIVE;
+
+    /** If the primary fails, we will queue items before reconnect.  This limits the number of items that can be queued. */
+    private int zombieQueueMaxSize = DEFAULT_ZOMBIE_QUEUE_MAX_SIZE;
+
+    /** Default constructor for the RemoteCacheAttributes object */
+    public RemoteCacheAttributes()
+    {
+        super();
+    }
+
+    /**
+     * Gets the failoverIndex attribute of the RemoteCacheAttributes object.
+     * <p>
+     * @return The failoverIndex value
+     */
+    @Override
+    public int getFailoverIndex()
+    {
+        return failoverIndex;
+    }
+
+    /**
+     * Sets the failoverIndex attribute of the RemoteCacheAttributes object.
+     * <p>
+     * @param p The new failoverIndex value
+     */
+    @Override
+    public void setFailoverIndex( int p )
+    {
+        this.failoverIndex = p;
+    }
+
+    /**
+     * Gets the failovers attribute of the RemoteCacheAttributes object.
+     * <p>
+     * @return The failovers value
+     */
+    @Override
+    public List<RemoteLocation> getFailovers()
+    {
+        return this.failovers;
+    }
+
+    /**
+     * Sets the failovers attribute of the RemoteCacheAttributes object.
+     * <p>
+     * @param failovers The new failovers value
+     */
+    @Override
+    public void setFailovers( List<RemoteLocation> failovers )
+    {
+        this.failovers = failovers;
+    }
+
+    /**
+     * Gets the failoverServers attribute of the RemoteCacheAttributes object.
+     * <p>
+     * @return The failoverServers value
+     */
+    @Override
+    public String getFailoverServers()
+    {
+        return this.failoverServers;
+    }
+
+    /**
+     * Sets the failoverServers attribute of the RemoteCacheAttributes object.
+     * <p>
+     * @param s The new failoverServers value
+     */
+    @Override
+    public void setFailoverServers( String s )
+    {
+        this.failoverServers = s;
+    }
+
+    /**
+     * Gets the localPort attribute of the RemoteCacheAttributes object.
+     * <p>
+     * @return The localPort value
+     */
+    @Override
+    public int getLocalPort()
+    {
+        return this.localPort;
+    }
+
+    /**
+     * Sets the localPort attribute of the RemoteCacheAttributes object
+     * @param p The new localPort value
+     */
+    @Override
+    public void setLocalPort( int p )
+    {
+        this.localPort = p;
+    }
+
+    /**
+     * @return the name of the pool
+     */
+    @Override
+    public String getThreadPoolName()
+    {
+        return threadPoolName;
+    }
+
+    /**
+     * @param name
+     */
+    @Override
+    public void setThreadPoolName( String name )
+    {
+        threadPoolName = name;
+    }
+
+    /**
+     * @return getTimeoutMillis
+     */
+    @Override
+    public int getGetTimeoutMillis()
+    {
+        return getTimeoutMillis;
+    }
+
+    /**
+     * @param millis
+     */
+    @Override
+    public void setGetTimeoutMillis( int millis )
+    {
+        getTimeoutMillis = millis;
+    }
+
+    /**
+     * By default this option is true. If you set it to false, you will not receive updates or
+     * removes from the remote server.
+     * <p>
+     * @param receive
+     */
+    @Override
+    public void setReceive( boolean receive )
+    {
+        this.receive = receive;
+    }
+
+    /**
+     * If RECEIVE is false then the remote cache will not register a listener with the remote
+     * server. This allows you to configure a remote server as a repository from which you can get
+     * and to which you put, but from which you do not receive any notifications. That is, you will
+     * not receive updates or removes.
+     * <p>
+     * If you set this option to false, you should set your local memory size to 0.
+     * <p>
+     * The remote cache manager uses this value to decide whether or not to register a listener.
+     * @return the receive value.
+     */
+    @Override
+    public boolean isReceive()
+    {
+        return this.receive;
+    }
+
+    /**
+     * The number of elements the zombie queue will hold. This queue is used to store events if we
+     * loose our connection with the server.
+     * <p>
+     * @param zombieQueueMaxSize The zombieQueueMaxSize to set.
+     */
+    @Override
+    public void setZombieQueueMaxSize( int zombieQueueMaxSize )
+    {
+        this.zombieQueueMaxSize = zombieQueueMaxSize;
+    }
+
+    /**
+     * The number of elements the zombie queue will hold. This queue is used to store events if we
+     * loose our connection with the server.
+     * <p>
+     * @return Returns the zombieQueueMaxSize.
+     */
+    @Override
+    public int getZombieQueueMaxSize()
+    {
+        return zombieQueueMaxSize;
+    }
+
+    /**
+     * @return String, all the important values that can be configured
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder(super.toString());
+        buf.append( "\n receive = [" + isReceive() + "]" );
+        buf.append( "\n getTimeoutMillis = [" + getGetTimeoutMillis() + "]" );
+        buf.append( "\n threadPoolName = [" + getThreadPoolName() + "]" );
+        buf.append( "\n localClusterConsistency = [" + isLocalClusterConsistency() + "]" );
+        buf.append( "\n zombieQueueMaxSize = [" + getZombieQueueMaxSize() + "]" );
+        return buf.toString();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheFactory.java
new file mode 100644
index 0000000..cbf76d9
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheFactory.java
@@ -0,0 +1,272 @@
+package org.apache.commons.jcs3.auxiliary.remote;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.rmi.registry.Registry;
+import java.util.ArrayList;
+import java.util.StringTokenizer;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCacheFactory;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCache;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.remote.server.behavior.RemoteType;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+
+/**
+ * The RemoteCacheFactory creates remote caches for the cache hub. It returns a no wait facade which
+ * is a wrapper around a no wait. The no wait object is either an active connection to a remote
+ * cache or a balking zombie if the remote cache is not accessible. It should be transparent to the
+ * clients.
+ */
+public class RemoteCacheFactory
+    extends AbstractAuxiliaryCacheFactory
+{
+    /** Monitor thread */
+    private RemoteCacheMonitor monitor;
+
+    /** Contains mappings of RemoteLocation instance to RemoteCacheManager instance. */
+    private ConcurrentMap<RemoteLocation, RemoteCacheManager> managers;
+
+    /** Lock for initialization of manager instances */
+    private Lock managerLock;
+
+    /**
+     * For LOCAL clients we get a handle to all the failovers, but we do not register a listener
+     * with them. We create the RemoteCacheManager, but we do not get a cache.
+     * <p>
+     * The failover runner will get a cache from the manager. When the primary is restored it will
+     * tell the manager for the failover to deregister the listener.
+     * <p>
+     * @param iaca
+     * @param cacheMgr
+     * @param cacheEventLogger
+     * @param elementSerializer
+     * @return AuxiliaryCache
+     */
+    @Override
+    public <K, V> AuxiliaryCache<K, V> createCache(
+            AuxiliaryCacheAttributes iaca, ICompositeCacheManager cacheMgr,
+           ICacheEventLogger cacheEventLogger, IElementSerializer elementSerializer )
+    {
+        RemoteCacheAttributes rca = (RemoteCacheAttributes) iaca;
+
+        ArrayList<RemoteCacheNoWait<K,V>> noWaits = new ArrayList<>();
+
+        switch (rca.getRemoteType())
+        {
+            case LOCAL:
+                // a list to be turned into an array of failover server information
+                ArrayList<RemoteLocation> failovers = new ArrayList<>();
+
+                // not necessary if a failover list is defined
+                // REGISTER PRIMARY LISTENER
+                // if it is a primary
+                boolean primaryDefined = false;
+                if ( rca.getRemoteLocation() != null )
+                {
+                    primaryDefined = true;
+
+                    failovers.add( rca.getRemoteLocation() );
+                    RemoteCacheManager rcm = getManager( rca, cacheMgr, cacheEventLogger, elementSerializer );
+                    RemoteCacheNoWait<K,V> ic = rcm.getCache( rca );
+                    noWaits.add( ic );
+                }
+
+                // GET HANDLE BUT DONT REGISTER A LISTENER FOR FAILOVERS
+                String failoverList = rca.getFailoverServers();
+                if ( failoverList != null )
+                {
+                    StringTokenizer fit = new StringTokenizer( failoverList, "," );
+                    int fCnt = 0;
+                    while ( fit.hasMoreTokens() )
+                    {
+                        fCnt++;
+
+                        String server = fit.nextToken();
+                        RemoteLocation location = RemoteLocation.parseServerAndPort(server);
+
+                        if (location != null)
+                        {
+                            failovers.add( location );
+                            rca.setRemoteLocation(location);
+                            RemoteCacheManager rcm = getManager( rca, cacheMgr, cacheEventLogger, elementSerializer );
+
+                            // add a listener if there are none, need to tell rca what
+                            // number it is at
+                            if ( ( !primaryDefined && fCnt == 1 ) || noWaits.size() <= 0 )
+                            {
+                                RemoteCacheNoWait<K,V> ic = rcm.getCache( rca );
+                                noWaits.add( ic );
+                            }
+                        }
+                    }
+                    // end while
+                }
+                // end if failoverList != null
+
+                rca.setFailovers( failovers );
+                break;
+
+            case CLUSTER:
+                // REGISTER LISTENERS FOR EACH SYSTEM CLUSTERED CACHEs
+                StringTokenizer it = new StringTokenizer( rca.getClusterServers(), "," );
+                while ( it.hasMoreElements() )
+                {
+                    String server = (String) it.nextElement();
+                    RemoteLocation location = RemoteLocation.parseServerAndPort(server);
+
+                    if (location != null)
+                    {
+                        rca.setRemoteLocation(location);
+                        RemoteCacheManager rcm = getManager( rca, cacheMgr, cacheEventLogger, elementSerializer );
+                        rca.setRemoteType( RemoteType.CLUSTER );
+                        RemoteCacheNoWait<K,V> ic = rcm.getCache( rca );
+                        noWaits.add( ic );
+                    }
+                }
+                break;
+        }
+
+        RemoteCacheNoWaitFacade<K, V> rcnwf =
+            new RemoteCacheNoWaitFacade<>(noWaits, rca, cacheEventLogger, elementSerializer, this );
+
+        return rcnwf;
+    }
+
+    // end createCache
+
+    /**
+     * Returns an instance of RemoteCacheManager for the given connection parameters.
+     * <p>
+     * Host and Port uniquely identify a manager instance.
+     * <p>
+     * @param cattr
+     *
+     * @return The instance value or null if no such manager exists
+     */
+    public RemoteCacheManager getManager( IRemoteCacheAttributes cattr )
+    {
+        if ( cattr.getRemoteLocation() == null )
+        {
+            cattr.setRemoteLocation("", Registry.REGISTRY_PORT);
+        }
+
+        RemoteLocation loc = cattr.getRemoteLocation();
+        RemoteCacheManager ins = managers.get( loc );
+
+        return ins;
+    }
+
+    /**
+     * Returns an instance of RemoteCacheManager for the given connection parameters.
+     * <p>
+     * Host and Port uniquely identify a manager instance.
+     * <p>
+     * If the connection cannot be established, zombie objects will be used for future recovery
+     * purposes.
+     * <p>
+     * @param cattr
+     * @param cacheMgr
+     * @param cacheEventLogger
+     * @param elementSerializer
+     * @return The instance value, never null
+     */
+    public RemoteCacheManager getManager( IRemoteCacheAttributes cattr, ICompositeCacheManager cacheMgr,
+                                                  ICacheEventLogger cacheEventLogger,
+                                                  IElementSerializer elementSerializer )
+    {
+        RemoteCacheManager ins = getManager( cattr );
+
+        if ( ins == null )
+        {
+            managerLock.lock();
+
+            try
+            {
+                ins = managers.get( cattr.getRemoteLocation() );
+
+                if (ins == null)
+                {
+                    ins = new RemoteCacheManager( cattr, cacheMgr, monitor, cacheEventLogger, elementSerializer);
+                    managers.put( cattr.getRemoteLocation(), ins );
+                    monitor.addManager(ins);
+                }
+            }
+            finally
+            {
+                managerLock.unlock();
+            }
+        }
+
+        return ins;
+    }
+
+	/**
+	 * @see org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCacheFactory#initialize()
+	 */
+	@Override
+	public void initialize()
+	{
+		super.initialize();
+
+		managers = new ConcurrentHashMap<>();
+		managerLock = new ReentrantLock();
+
+        monitor = new RemoteCacheMonitor();
+        monitor.setDaemon(true);
+	}
+
+	/**
+	 * @see org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCacheFactory#dispose()
+	 */
+	@Override
+	public void dispose()
+	{
+		for (RemoteCacheManager manager : managers.values())
+		{
+			manager.release();
+		}
+
+		managers.clear();
+
+        if (monitor != null)
+        {
+            monitor.notifyShutdown();
+            try
+            {
+                monitor.join(5000);
+            }
+            catch (InterruptedException e)
+            {
+                // swallow
+            }
+            monitor = null;
+        }
+
+		super.dispose();
+	}
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheFailoverRunner.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheFailoverRunner.java
new file mode 100644
index 0000000..909bf6b
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheFailoverRunner.java
@@ -0,0 +1,389 @@
+package org.apache.commons.jcs3.auxiliary.remote;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.List;
+import java.util.ListIterator;
+
+import org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCacheMonitor;
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheAttributes;
+import org.apache.commons.jcs3.engine.CacheStatus;
+import org.apache.commons.jcs3.engine.behavior.ICache;
+
+/**
+ * The RemoteCacheFailoverRunner tries to establish a connection with a failover
+ * server, if any are defined. Once a failover connection is made, it will
+ * attempt to replace the failover with the primary remote server.
+ * <p>
+ * It works by switching out the RemoteCacheNoWait inside the Facade.
+ * <p>
+ * Client (i.e.) the CompositeCache has reference to a RemoteCacheNoWaitFacade.
+ * This facade is created by the RemoteCacheFactory. The factory maintains a set
+ * of managers, one for each remote server. Typically, there will only be one
+ * manager.
+ * <p>
+ * If you use multiple remote servers, you may want to set one or more as
+ * failovers. If a local cache cannot connect to the primary server, or looses
+ * its connection to the primary server, it will attempt to restore that
+ * Connection in the background. If failovers are defined, the Failover runner
+ * will try to connect to a failover until the primary is restored.
+ *
+ */
+public class RemoteCacheFailoverRunner<K, V> extends AbstractAuxiliaryCacheMonitor
+{
+    /** The facade returned to the composite cache. */
+    private final RemoteCacheNoWaitFacade<K, V> facade;
+
+    /** Factory instance */
+    private final RemoteCacheFactory cacheFactory;
+
+    /**
+     * Constructor for the RemoteCacheFailoverRunner object. This allows the
+     * FailoverRunner to modify the facade that the CompositeCache references.
+     *
+     * @param facade the facade the CompositeCache talks to.
+     * @param cacheFactory the cache factory instance
+     */
+    public RemoteCacheFailoverRunner( RemoteCacheNoWaitFacade<K, V> facade, RemoteCacheFactory cacheFactory )
+    {
+        super("JCS-RemoteCacheFailoverRunner");
+        this.facade = facade;
+        this.cacheFactory = cacheFactory;
+        setIdlePeriod(20000L);
+    }
+
+    /**
+     * Clean up all resources before shutdown
+     */
+    @Override
+    protected void dispose()
+    {
+        // empty
+    }
+
+    /**
+     * do actual work
+     */
+    @Override
+    protected void doWork()
+    {
+        // empty
+    }
+
+
+    /**
+     * Main processing method for the RemoteCacheFailoverRunner object.
+     * <p>
+     * If we do not have a connection with any failover server, this will try to
+     * connect one at a time. If no connection can be made, it goes to sleep for
+     * a while (20 seconds).
+     * <p>
+     * Once a connection with a failover is made, we will try to reconnect to
+     * the primary server.
+     * <p>
+     * The primary server is the first server defines in the FailoverServers
+     * list.
+     */
+    @Override
+    public void run()
+    {
+        // start the main work of connecting to a failover and then restoring
+        // the primary.
+        connectAndRestore();
+
+        if ( log.isInfoEnabled() )
+        {
+            int failoverIndex = facade.getAuxiliaryCacheAttributes().getFailoverIndex();
+            log.info( "Exiting failover runner. Failover index = {0}", failoverIndex);
+
+            if ( failoverIndex <= 0 )
+            {
+                log.info( "Failover index is <= 0, meaning we are not connected to a failover server." );
+            }
+            else if ( failoverIndex > 0 )
+            {
+                log.info( "Failover index is > 0, meaning we are connected to a failover server." );
+            }
+            // log if we are allright or not.
+        }
+    }
+
+    /**
+     * This is the main loop. If there are failovers defined, then this will
+     * continue until the primary is re-connected. If no failovers are defined,
+     * this will exit automatically.
+     */
+    private void connectAndRestore()
+    {
+        IRemoteCacheAttributes rca0 = facade.getAuxiliaryCacheAttributes();
+
+        do
+        {
+            log.info( "Remote cache FAILOVER RUNNING." );
+
+            // there is no active listener
+            if ( !allright.get() )
+            {
+                // Monitor each RemoteCacheManager instance one after the other.
+                // Each RemoteCacheManager corresponds to one remote connection.
+                List<RemoteLocation> failovers = rca0.getFailovers();
+                // we should probably check to see if there are any failovers,
+                // even though the caller
+                // should have already.
+
+                if ( failovers == null )
+                {
+                    log.warn( "Remote is misconfigured, failovers was null." );
+                    return;
+                }
+                else if ( failovers.size() == 1 )
+                {
+                    // if there is only the primary, return out of this
+                    log.info( "No failovers defined, exiting failover runner." );
+                    return;
+                }
+
+                int fidx = rca0.getFailoverIndex();
+                log.debug( "fidx = {0} failovers.size = {1}", () -> fidx,
+                        () -> failovers.size() );
+
+                // shouldn't we see if the primary is backup?
+                // If we don't check the primary, if it gets connected in the
+                // background,
+                // we will disconnect it only to put it right back
+                ListIterator<RemoteLocation> i = failovers.listIterator(fidx); // + 1; // +1 skips the primary
+                log.debug( "starting at failover i = {0}", i );
+
+                // try them one at a time until successful
+                for ( ; i.hasNext() && !allright.get();)
+                {
+                    RemoteLocation server = i.next();
+                    log.debug( "Trying server [{1}] at failover index i = {1}",
+                            server, i );
+
+                    RemoteCacheAttributes rca = (RemoteCacheAttributes) rca0.clone();
+                    rca.setRemoteLocation(server);
+                    RemoteCacheManager rcm = cacheFactory.getManager( rca );
+
+                    log.debug( "RemoteCacheAttributes for failover = {0}", rca );
+
+                    if (rcm != null)
+                    {
+                        // add a listener if there are none, need to tell rca
+                        // what number it is at
+                        ICache<K, V> ic = rcm.getCache( rca );
+                        if ( ic.getStatus() == CacheStatus.ALIVE )
+                        {
+                            // may need to do this more gracefully
+                            log.debug( "resetting no wait" );
+                            facade.restorePrimaryServer((RemoteCacheNoWait<K, V>) ic);
+                            rca0.setFailoverIndex( i.nextIndex() );
+
+                            log.debug( "setting ALLRIGHT to true" );
+                            if ( i.hasPrevious() )
+                            {
+                                log.debug( "Moving to Primary Recovery Mode, failover index = {0}", i );
+                            }
+                            else
+                            {
+                                log.debug( "No need to connect to failover, the primary server is back up." );
+                            }
+
+                            allright.set(true);
+
+                            log.info( "CONNECTED to host = [{0}]",
+                                    () -> rca.getRemoteLocation() );
+                        }
+                    }
+                }
+            }
+            // end if !allright
+            // get here if while index >0 and allright, meaning that we are
+            // connected to some backup server.
+            else
+            {
+                log.debug( "ALLRIGHT is true " );
+                log.info( "Failover runner is in primary recovery mode. "
+                        + "Failover index = {0} Will now try to reconnect to "
+                        + "primary server.", () -> rca0.getFailoverIndex() );
+            }
+
+            boolean primaryRestoredSuccessfully = false;
+            // if we are not connected to the primary, try.
+            if ( rca0.getFailoverIndex() > 0 )
+            {
+                primaryRestoredSuccessfully = restorePrimary();
+                log.debug( "Primary recovery success state = {0}",
+                        primaryRestoredSuccessfully );
+            }
+
+            if ( !primaryRestoredSuccessfully )
+            {
+                // Time driven mode: sleep between each round of recovery
+                // attempt.
+                try
+                {
+                    log.warn( "Failed to reconnect to primary server. "
+                            + "Cache failover runner is going to sleep for "
+                            + "{0} milliseconds.", idlePeriod );
+                    Thread.sleep( idlePeriod );
+                }
+                catch ( InterruptedException ex )
+                {
+                    // ignore;
+                }
+            }
+
+            // try to bring the listener back to the primary
+        }
+        while ( rca0.getFailoverIndex() > 0 || !allright.get() );
+        // continue if the primary is not restored or if things are not allright.
+    }
+
+    /**
+     * Try to restore the primary server.
+     * <p>
+     * Once primary is restored the failover listener must be deregistered.
+     * <p>
+     * The primary server is the first server defines in the FailoverServers
+     * list.
+     *
+     * @return boolean value indicating whether the restoration was successful
+     */
+    private boolean restorePrimary()
+    {
+        IRemoteCacheAttributes rca0 = facade.getAuxiliaryCacheAttributes();
+        // try to move back to the primary
+        RemoteLocation server = rca0.getFailovers().get(0);
+
+        log.info( "Trying to restore connection to primary remote server "
+                + "[{0}]", server );
+
+        RemoteCacheAttributes rca = (RemoteCacheAttributes) rca0.clone();
+        rca.setRemoteLocation(server);
+        RemoteCacheManager rcm = cacheFactory.getManager( rca );
+
+        if (rcm != null)
+        {
+            // add a listener if there are none, need to tell rca what number it
+            // is at
+            ICache<K, V> ic = rcm.getCache( rca );
+            // by default the listener id should be 0, else it will be the
+            // listener
+            // Originally associated with the remote cache. either way is fine.
+            // We just don't want the listener id from a failover being used.
+            // If the remote server was rebooted this could be a problem if new
+            // locals were also added.
+
+            if ( ic.getStatus() == CacheStatus.ALIVE )
+            {
+                try
+                {
+                    // we could have more than one listener registered right
+                    // now.
+                    // this will not result in a loop, only duplication
+                    // stop duplicate listening.
+                    if ( facade.getPrimaryServer() != null && facade.getPrimaryServer().getStatus() == CacheStatus.ALIVE )
+                    {
+                        int fidx = rca0.getFailoverIndex();
+
+                        if ( fidx > 0 )
+                        {
+                            RemoteLocation serverOld = rca0.getFailovers().get(fidx);
+
+                            log.debug( "Failover Index = {0} the server at that "
+                                    + "index is [{1}]", fidx, serverOld );
+
+                            if ( serverOld != null )
+                            {
+                                // create attributes that reflect the
+                                // previous failed over configuration.
+                                RemoteCacheAttributes rcaOld = (RemoteCacheAttributes) rca0.clone();
+                                rcaOld.setRemoteLocation(serverOld);
+                                RemoteCacheManager rcmOld = cacheFactory.getManager( rcaOld );
+
+                                if ( rcmOld != null )
+                                {
+                                    // manager can remove by name if
+                                    // necessary
+                                    rcmOld.removeRemoteCacheListener( rcaOld );
+                                }
+                                log.info( "Successfully deregistered from "
+                                        + "FAILOVER remote server = {0}", serverOld );
+                            }
+                        }
+                        else if ( fidx == 0 )
+                        {
+                            // this should never happen. If there are no
+                            // failovers this shouldn't get called.
+                            if ( log.isDebugEnabled() )
+                            {
+                                log.debug( "No need to restore primary, it is already restored." );
+                                return true;
+                            }
+                        }
+                        else if ( fidx < 0 )
+                        {
+                            // this should never happen
+                            log.warn( "Failover index is less than 0, this shouldn't happen" );
+                        }
+                    }
+                }
+                catch ( IOException e )
+                {
+                    // TODO, should try again, or somehow stop the listener
+                    log.error("Trouble trying to deregister old failover "
+                            + "listener prior to restoring the primary = {0}",
+                            server, e );
+                }
+
+                // Restore primary
+                // may need to do this more gracefully, letting the failover finish in the background
+                RemoteCacheNoWait<K, V> failoverNoWait = facade.getPrimaryServer();
+
+                // swap in a new one
+                facade.restorePrimaryServer((RemoteCacheNoWait<K, V>) ic);
+                rca0.setFailoverIndex( 0 );
+
+                String message = "Successfully reconnected to PRIMARY "
+                        + "remote server. Substituted primary for "
+                        + "failoverNoWait [" + failoverNoWait + "]";
+                log.info( message );
+
+                if ( facade.getCacheEventLogger() != null )
+                {
+                    facade.getCacheEventLogger().logApplicationEvent(
+                            "RemoteCacheFailoverRunner", "RestoredPrimary",
+                            message );
+                }
+                return true;
+            }
+        }
+
+        // else all right
+        // if the failover index was at 0 here, we would be in a bad
+        // situation, unless there were just
+        // no failovers configured.
+        log.debug( "Primary server status in error, not connected." );
+
+        return false;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheListener.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheListener.java
new file mode 100644
index 0000000..4b5a93b
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheListener.java
@@ -0,0 +1,115 @@
+package org.apache.commons.jcs3.auxiliary.remote;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheConstants;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * Registered with RemoteCache server. The server updates the local caches via this listener. Each
+ * server assigns a unique listener id for a listener.
+ * <p>
+ * One listener is used per remote cache server. The same listener is used for all the regions that
+ * talk to a particular server.
+ */
+public class RemoteCacheListener<K, V>
+    extends AbstractRemoteCacheListener<K, V>
+    implements IRemoteCacheConstants
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog( RemoteCacheListener.class );
+
+    /** Has this client been shutdown. */
+    private boolean disposed = false;
+
+    /**
+     * Only need one since it does work for all regions, just reference by multiple region names.
+     * <p>
+     * The constructor exports this object, making it available to receive incoming calls. The
+     * callback port is anonymous unless a local port value was specified in the configuration.
+     * <p>
+     * @param irca cache configuration
+     * @param cacheMgr the cache hub
+     * @param elementSerializer a custom serializer
+     */
+    public RemoteCacheListener( IRemoteCacheAttributes irca, ICompositeCacheManager cacheMgr, IElementSerializer elementSerializer )
+    {
+        super( irca, cacheMgr, elementSerializer );
+
+        // Export this remote object to make it available to receive incoming
+        // calls.
+        try
+        {
+            UnicastRemoteObject.exportObject( this, irca.getLocalPort() );
+        }
+        catch ( RemoteException ex )
+        {
+            log.error( "Problem exporting object.", ex );
+            throw new IllegalStateException( ex.getMessage() );
+        }
+    }
+
+    /**
+     * Deregister itself.
+     * <p>
+     * @throws IOException
+     */
+    @Override
+    public synchronized void dispose()
+        throws IOException
+    {
+        if ( !disposed )
+        {
+            log.info( "Unexporting listener." );
+            try
+            {
+                UnicastRemoteObject.unexportObject( this, true );
+            }
+            catch ( RemoteException ex )
+            {
+                log.error( "Problem unexporting the listener.", ex );
+                throw new IllegalStateException( ex.getMessage() );
+            }
+            disposed = true;
+        }
+    }
+
+    /**
+     * For easier debugging.
+     * <p>
+     * @return Basic info on this listener.
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        buf.append( "\n RemoteCacheListener: " );
+        buf.append( super.toString() );
+        return buf.toString();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheManager.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheManager.java
new file mode 100644
index 0000000..84119e2
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheManager.java
@@ -0,0 +1,348 @@
+package org.apache.commons.jcs3.auxiliary.remote;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.rmi.Naming;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheClient;
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheListener;
+import org.apache.commons.jcs3.engine.CacheStatus;
+import org.apache.commons.jcs3.engine.CacheWatchRepairable;
+import org.apache.commons.jcs3.engine.ZombieCacheServiceNonLocal;
+import org.apache.commons.jcs3.engine.ZombieCacheWatch;
+import org.apache.commons.jcs3.engine.behavior.ICacheObserver;
+import org.apache.commons.jcs3.engine.behavior.ICacheServiceNonLocal;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * An instance of RemoteCacheManager corresponds to one remote connection of a specific host and
+ * port. All RemoteCacheManager instances are monitored by the singleton RemoteCacheMonitor
+ * monitoring daemon for error detection and recovery.
+ * <p>
+ * Getting an instance of the remote cache has the effect of getting a handle on the remote server.
+ * Listeners are not registered with the server until a cache is requested from the manager.
+ */
+public class RemoteCacheManager
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog( RemoteCacheManager.class );
+
+    /** Contains instances of RemoteCacheNoWait managed by a RemoteCacheManager instance. */
+    private final ConcurrentMap<String, RemoteCacheNoWait<?, ?>> caches =
+            new ConcurrentHashMap<>();
+
+    /** The event logger. */
+    private final ICacheEventLogger cacheEventLogger;
+
+    /** The serializer. */
+    private final IElementSerializer elementSerializer;
+
+    /** Handle to the remote cache service; or a zombie handle if failed to connect. */
+    private ICacheServiceNonLocal<?, ?> remoteService;
+
+    /**
+     * Wrapper of the remote cache watch service; or wrapper of a zombie service if failed to
+     * connect.
+     */
+    private final CacheWatchRepairable remoteWatch;
+
+    /** The cache manager listeners will need to use to get a cache. */
+    private final ICompositeCacheManager cacheMgr;
+
+    /** For error notification */
+    private final RemoteCacheMonitor monitor;
+
+    /** The service found through lookup */
+    private final String registry;
+
+    /** can it be restored */
+    private boolean canFix = true;
+
+    /**
+     * Constructs an instance to with the given remote connection parameters. If the connection
+     * cannot be made, "zombie" services will be temporarily used until a successful re-connection
+     * is made by the monitoring daemon.
+     * <p>
+     * @param cattr cache attributes
+     * @param cacheMgr the cache hub
+     * @param monitor the cache monitor thread for error notifications
+     * @param cacheEventLogger
+     * @param elementSerializer
+     */
+    protected RemoteCacheManager( IRemoteCacheAttributes cattr, ICompositeCacheManager cacheMgr,
+                                RemoteCacheMonitor monitor,
+                                ICacheEventLogger cacheEventLogger, IElementSerializer elementSerializer)
+    {
+        this.cacheMgr = cacheMgr;
+        this.monitor = monitor;
+        this.cacheEventLogger = cacheEventLogger;
+        this.elementSerializer = elementSerializer;
+        this.remoteWatch = new CacheWatchRepairable();
+
+        this.registry = RemoteUtils.getNamingURL(cattr.getRemoteLocation(), cattr.getRemoteServiceName());
+
+        try
+        {
+            lookupRemoteService();
+        }
+        catch (IOException e)
+        {
+            log.error("Could not find server", e);
+            // Notify the cache monitor about the error, and kick off the
+            // recovery process.
+            monitor.notifyError();
+        }
+    }
+
+    /**
+     * Lookup remote service from registry
+     * @throws IOException if the remote service could not be found
+     *
+     */
+    protected void lookupRemoteService() throws IOException
+    {
+        log.info( "Looking up server [{0}]", registry );
+        try
+        {
+            Object obj = Naming.lookup( registry );
+            log.info( "Server found: {0}", obj );
+
+            // Successful connection to the remote server.
+            this.remoteService = (ICacheServiceNonLocal<?, ?>) obj;
+            log.debug( "Remote Service = {0}", remoteService );
+            remoteWatch.setCacheWatch( (ICacheObserver) remoteService );
+        }
+        catch ( Exception ex )
+        {
+            // Failed to connect to the remote server.
+            // Configure this RemoteCacheManager instance to use the "zombie"
+            // services.
+            this.remoteService = new ZombieCacheServiceNonLocal<>();
+            remoteWatch.setCacheWatch( new ZombieCacheWatch() );
+            throw new IOException( "Problem finding server at [" + registry + "]", ex );
+        }
+    }
+
+    /**
+     * Adds the remote cache listener to the underlying cache-watch service.
+     * <p>
+     * @param cattr The feature to be added to the RemoteCacheListener attribute
+     * @param listener The feature to be added to the RemoteCacheListener attribute
+     * @throws IOException
+     */
+    public <K, V> void addRemoteCacheListener( IRemoteCacheAttributes cattr, IRemoteCacheListener<K, V> listener )
+        throws IOException
+    {
+        if ( cattr.isReceive() )
+        {
+            log.info( "The remote cache is configured to receive events from the remote server. "
+                + "We will register a listener. remoteWatch = {0} | IRemoteCacheListener = {1}"
+                + " | cacheName ", remoteWatch, listener, cattr.getCacheName() );
+
+            remoteWatch.addCacheListener( cattr.getCacheName(), listener );
+        }
+        else
+        {
+            log.info( "The remote cache is configured to NOT receive events from the remote server. "
+                    + "We will NOT register a listener." );
+        }
+    }
+
+    /**
+     * Removes a listener. When the primary recovers the failover must deregister itself for a
+     * region. The failover runner will call this method to de-register. We do not want to deregister
+     * all listeners to a remote server, in case a failover is a primary of another region. Having
+     * one regions failover act as another servers primary is not currently supported.
+     * <p>
+     * @param cattr
+     * @throws IOException
+     */
+    public void removeRemoteCacheListener( IRemoteCacheAttributes cattr )
+        throws IOException
+    {
+        RemoteCacheNoWait<?, ?> cache = caches.get( cattr.getCacheName() );
+        if ( cache != null )
+        {
+        	removeListenerFromCache(cache);
+        }
+        else
+        {
+            if ( cattr.isReceive() )
+            {
+                log.warn( "Trying to deregister Cache Listener that was never registered." );
+            }
+            else
+            {
+                log.debug( "Since the remote cache is configured to not receive, "
+                    + "there is no listener to deregister." );
+            }
+        }
+    }
+
+    // common helper method
+	private void removeListenerFromCache(RemoteCacheNoWait<?, ?> cache) throws IOException
+	{
+		IRemoteCacheClient<?, ?> rc = cache.getRemoteCache();
+	    log.debug( "Found cache for [{0}], deregistering listener.",
+	            () -> cache.getCacheName() );
+		// could also store the listener for a server in the manager.
+		IRemoteCacheListener<?, ?> listener = rc.getListener();
+        remoteWatch.removeCacheListener( cache.getCacheName(), listener );
+	}
+
+    /**
+     * Gets a RemoteCacheNoWait from the RemoteCacheManager. The RemoteCacheNoWait objects are
+     * identified by the cache name value of the RemoteCacheAttributes object.
+     * <p>
+     * If the client is configured to register a listener, this call results on a listener being
+     * created if one isn't already registered with the remote cache for this region.
+     * <p>
+     * @param cattr
+     * @return The cache value
+     */
+    @SuppressWarnings("unchecked") // Need to cast because of common map for all caches
+    public <K, V> RemoteCacheNoWait<K, V> getCache( IRemoteCacheAttributes cattr )
+    {
+        RemoteCacheNoWait<K, V> remoteCacheNoWait =
+                (RemoteCacheNoWait<K, V>) caches.computeIfAbsent(cattr.getCacheName(), key -> {
+                    return newRemoteCacheNoWait(cattr);
+                });
+
+        // might want to do some listener sanity checking here.
+        return remoteCacheNoWait;
+    }
+
+    /**
+     * Create new RemoteCacheNoWait instance
+     *
+     * @param cattr the cache configuration
+     * @return the instance
+     */
+    protected <K, V> RemoteCacheNoWait<K, V> newRemoteCacheNoWait(IRemoteCacheAttributes cattr)
+    {
+        RemoteCacheNoWait<K, V> remoteCacheNoWait;
+        // create a listener first and pass it to the remotecache
+        // sender.
+        RemoteCacheListener<K, V> listener = null;
+        try
+        {
+            listener = new RemoteCacheListener<>( cattr, cacheMgr, elementSerializer );
+            addRemoteCacheListener( cattr, listener );
+        }
+        catch ( Exception e )
+        {
+            log.error( "Problem adding listener. RemoteCacheListener = {0}",
+                    listener, e );
+        }
+
+        IRemoteCacheClient<K, V> remoteCacheClient =
+            new RemoteCache<>( cattr, (ICacheServiceNonLocal<K, V>) remoteService, listener, monitor );
+        remoteCacheClient.setCacheEventLogger( cacheEventLogger );
+        remoteCacheClient.setElementSerializer( elementSerializer );
+
+        remoteCacheNoWait = new RemoteCacheNoWait<>( remoteCacheClient );
+        remoteCacheNoWait.setCacheEventLogger( cacheEventLogger );
+        remoteCacheNoWait.setElementSerializer( elementSerializer );
+
+        return remoteCacheNoWait;
+    }
+
+    /** Shutdown all. */
+    public void release()
+    {
+        for (RemoteCacheNoWait<?, ?> c : caches.values())
+        {
+            try
+            {
+                log.info( "freeCache [{0}]", () -> c.getCacheName() );
+
+                removeListenerFromCache(c);
+                c.dispose();
+            }
+            catch ( IOException ex )
+            {
+                log.error( "Problem releasing {0}", c.getCacheName(), ex );
+            }
+        }
+
+        caches.clear();
+    }
+
+    /**
+     * Fixes up all the caches managed by this cache manager.
+     */
+    public void fixCaches()
+    {
+        if ( !canFix )
+        {
+            return;
+        }
+
+        log.info( "Fixing caches. ICacheServiceNonLocal {0} | IRemoteCacheObserver {1}",
+                remoteService, remoteWatch );
+
+        for (RemoteCacheNoWait<?, ?> c : caches.values())
+        {
+            if (c.getStatus() == CacheStatus.ERROR)
+            {
+                c.fixCache( remoteService );
+            }
+        }
+
+        if ( log.isInfoEnabled() )
+        {
+            String msg = "Remote connection to " + registry + " resumed.";
+            if ( cacheEventLogger != null )
+            {
+                cacheEventLogger.logApplicationEvent( "RemoteCacheManager", "fix", msg );
+            }
+            log.info( msg );
+        }
+    }
+
+    /**
+     * Returns true if the connection to the remote host can be
+     * successfully re-established.
+     * <p>
+     * @return true if we found a failover server
+     */
+    public boolean canFixCaches()
+    {
+        try
+        {
+            lookupRemoteService();
+        }
+        catch (IOException e)
+        {
+            log.error("Could not find server", e);
+            canFix = false;
+        }
+
+        return canFix;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheMonitor.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheMonitor.java
new file mode 100644
index 0000000..8ef72ef
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheMonitor.java
@@ -0,0 +1,100 @@
+package org.apache.commons.jcs3.auxiliary.remote;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCacheMonitor;
+
+/**
+ * Used to monitor and repair any failed connection for the remote cache service. By default the
+ * monitor operates in a failure driven mode. That is, it goes into a wait state until there is an
+ * error.
+ *
+ * TODO consider moving this into an active monitoring mode. Upon the notification of a
+ * connection error, the monitor changes to operate in a time driven mode. That is, it attempts to
+ * recover the connections on a periodic basis. When all failed connections are restored, it changes
+ * back to the failure driven mode.
+ */
+public class RemoteCacheMonitor extends AbstractAuxiliaryCacheMonitor
+{
+    /**
+     * Map of managers to monitor
+     */
+    private final ConcurrentHashMap<RemoteCacheManager, RemoteCacheManager> managers;
+
+    /** Constructor for the RemoteCacheMonitor object */
+    public RemoteCacheMonitor()
+    {
+        super("JCS-RemoteCacheMonitor");
+        this.managers = new ConcurrentHashMap<>();
+        setIdlePeriod(30000L);
+    }
+
+    /**
+     * Add a manager to be monitored
+     *
+     * @param manager the remote cache manager
+     */
+    public void addManager(RemoteCacheManager manager)
+    {
+        this.managers.put(manager, manager);
+
+        // if not yet started, go ahead
+        if (this.getState() == Thread.State.NEW)
+        {
+            this.start();
+        }
+    }
+
+    /**
+     * Clean up all resources before shutdown
+     */
+    @Override
+    public void dispose()
+    {
+        this.managers.clear();
+    }
+
+    // Avoid the use of any synchronization in the process of monitoring for
+    // performance reason.
+    // If exception is thrown owing to synchronization,
+    // just skip the monitoring until the next round.
+    /** Main processing method for the RemoteCacheMonitor object */
+    @Override
+    public void doWork()
+    {
+        // Monitor each RemoteCacheManager instance one after the other.
+        // Each RemoteCacheManager corresponds to one remote connection.
+        for (RemoteCacheManager mgr : managers.values())
+        {
+            // If we can't fix them, just skip and re-try in
+            // the next round.
+            if ( mgr.canFixCaches() )
+            {
+                mgr.fixCaches();
+            }
+            else
+            {
+                allright.set(false);
+            }
+        }
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheNoWait.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheNoWait.java
new file mode 100644
index 0000000..bf683a9
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheNoWait.java
@@ -0,0 +1,517 @@
+package org.apache.commons.jcs3.auxiliary.remote;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.rmi.UnmarshalException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCache;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheClient;
+import org.apache.commons.jcs3.engine.CacheAdaptor;
+import org.apache.commons.jcs3.engine.CacheEventQueueFactory;
+import org.apache.commons.jcs3.engine.CacheStatus;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheEventQueue;
+import org.apache.commons.jcs3.engine.behavior.ICacheServiceNonLocal;
+import org.apache.commons.jcs3.engine.stats.StatElement;
+import org.apache.commons.jcs3.engine.stats.Stats;
+import org.apache.commons.jcs3.engine.stats.behavior.IStatElement;
+import org.apache.commons.jcs3.engine.stats.behavior.IStats;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * The RemoteCacheNoWait wraps the RemoteCacheClient. The client holds a handle on the
+ * RemoteCacheService.
+ * <p>
+ * Used to queue up update requests to the underlying cache. These requests will be processed in
+ * their order of arrival via the cache event queue processor.
+ * <p>
+ * Typically errors will be handled down stream. We only need to kill the queue if an error makes it
+ * to this level from the queue. That can only happen if the queue is damaged, since the events are
+ * Processed asynchronously.
+ * <p>
+ * There is no reason to create a queue on startup if the remote is not healthy.
+ * <p>
+ * If the remote cache encounters an error it will zombie--create a balking facade for the service.
+ * The Zombie will queue up items until the connection is restored. An alternative way to accomplish
+ * the same thing would be to stop, not destroy the queue at this level. That way items would be
+ * added to the queue and then when the connection is restored, we could start the worker threads
+ * again. This is a better long term solution, but it requires some significant changes to the
+ * complicated worker queues.
+ */
+public class RemoteCacheNoWait<K, V>
+    extends AbstractAuxiliaryCache<K, V>
+{
+    /** log instance */
+    private static final Log log = LogManager.getLog( RemoteCacheNoWait.class );
+
+    /** The remote cache client */
+    private final IRemoteCacheClient<K, V> remoteCacheClient;
+
+    /** Event queue for queuing up calls like put and remove. */
+    private ICacheEventQueue<K, V> cacheEventQueue;
+
+    /** how many times get has been called. */
+    private int getCount = 0;
+
+    /** how many times getMatching has been called. */
+    private int getMatchingCount = 0;
+
+    /** how many times getMultiple has been called. */
+    private int getMultipleCount = 0;
+
+    /** how many times remove has been called. */
+    private int removeCount = 0;
+
+    /** how many times put has been called. */
+    private int putCount = 0;
+
+    /**
+     * Constructs with the given remote cache, and fires up an event queue for asynchronous
+     * processing.
+     * <p>
+     * @param cache
+     */
+    public RemoteCacheNoWait( IRemoteCacheClient<K, V> cache )
+    {
+        remoteCacheClient = cache;
+        this.cacheEventQueue = createCacheEventQueue(cache);
+
+        if ( remoteCacheClient.getStatus() == CacheStatus.ERROR )
+        {
+            cacheEventQueue.destroy();
+        }
+    }
+
+    /**
+     * Create a cache event queue from the parameters of the remote client
+     * @param client the remote client
+     */
+    private ICacheEventQueue<K, V> createCacheEventQueue( IRemoteCacheClient<K, V> client )
+    {
+        CacheEventQueueFactory<K, V> factory = new CacheEventQueueFactory<>();
+        ICacheEventQueue<K, V> ceq = factory.createCacheEventQueue(
+            new CacheAdaptor<>( client ),
+            client.getListenerId(),
+            client.getCacheName(),
+            client.getAuxiliaryCacheAttributes().getEventQueuePoolName(),
+            client.getAuxiliaryCacheAttributes().getEventQueueType() );
+        return ceq;
+    }
+
+    /**
+     * Adds a put event to the queue.
+     * <p>
+     * @param element
+     * @throws IOException
+     */
+    @Override
+    public void update( ICacheElement<K, V> element )
+        throws IOException
+    {
+        putCount++;
+        try
+        {
+            cacheEventQueue.addPutEvent( element );
+        }
+        catch ( IOException e )
+        {
+            log.error( "Problem adding putEvent to queue.", e );
+            cacheEventQueue.destroy();
+            throw e;
+        }
+    }
+
+    /**
+     * Synchronously reads from the remote cache.
+     * <p>
+     * @param key
+     * @return element from the remote cache, or null if not present
+     * @throws IOException
+     */
+    @Override
+    public ICacheElement<K, V> get( K key )
+        throws IOException
+    {
+        getCount++;
+        try
+        {
+            return remoteCacheClient.get( key );
+        }
+        catch ( UnmarshalException ue )
+        {
+            log.debug( "Retrying the get owing to UnmarshalException." );
+
+            try
+            {
+                return remoteCacheClient.get( key );
+            }
+            catch ( IOException ex )
+            {
+                log.info( "Failed in retrying the get for the second time. ", ex );
+            }
+        }
+        catch ( IOException ex )
+        {
+            // We don't want to destroy the queue on a get failure.
+            // The RemoteCache will Zombie and queue.
+            // Since get does not use the queue, I don't want to kill the queue.
+            throw ex;
+        }
+
+        return null;
+    }
+
+    /**
+     * @param pattern
+     * @return Map
+     * @throws IOException
+     *
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMatching( String pattern )
+        throws IOException
+    {
+        getMatchingCount++;
+        try
+        {
+            return remoteCacheClient.getMatching( pattern );
+        }
+        catch ( UnmarshalException ue )
+        {
+            log.debug( "Retrying the getMatching owing to UnmarshalException." );
+
+            try
+            {
+                return remoteCacheClient.getMatching( pattern );
+            }
+            catch ( IOException ex )
+            {
+                log.info( "Failed in retrying the getMatching for the second time.", ex );
+            }
+        }
+        catch ( IOException ex )
+        {
+            // We don't want to destroy the queue on a get failure.
+            // The RemoteCache will Zombie and queue.
+            // Since get does not use the queue, I don't want to kill the queue.
+            throw ex;
+        }
+
+        return Collections.emptyMap();
+    }
+
+    /**
+     * Gets multiple items from the cache based on the given set of keys. Sends the getMultiple
+     * request on to the server rather than looping through the requested keys.
+     * <p>
+     * @param keys
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache for any of these keys
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMultiple( Set<K> keys )
+        throws IOException
+    {
+        getMultipleCount++;
+        try
+        {
+            return remoteCacheClient.getMultiple( keys );
+        }
+        catch ( UnmarshalException ue )
+        {
+            log.debug( "Retrying the getMultiple owing to UnmarshalException..." );
+
+            try
+            {
+                return remoteCacheClient.getMultiple( keys );
+            }
+            catch ( IOException ex )
+            {
+                log.info( "Failed in retrying the getMultiple for the second time.", ex );
+            }
+        }
+        catch ( IOException ex )
+        {
+            // We don't want to destroy the queue on a get failure.
+            // The RemoteCache will Zombie and queue.
+            // Since get does not use the queue, I don't want to kill the queue.
+            throw ex;
+        }
+
+        return new HashMap<>();
+    }
+
+    /**
+     * Return the keys in this cache.
+     * <p>
+     * @see org.apache.commons.jcs3.auxiliary.AuxiliaryCache#getKeySet()
+     */
+    @Override
+    public Set<K> getKeySet() throws IOException
+    {
+        return remoteCacheClient.getKeySet();
+    }
+
+    /**
+     * Adds a remove request to the remote cache.
+     * <p>
+     * @param key
+     * @return if this was successful
+     * @throws IOException
+     */
+    @Override
+    public boolean remove( K key )
+        throws IOException
+    {
+        removeCount++;
+        try
+        {
+            cacheEventQueue.addRemoveEvent( key );
+        }
+        catch ( IOException e )
+        {
+            log.error( "Problem adding RemoveEvent to queue.", e );
+            cacheEventQueue.destroy();
+            throw e;
+        }
+        return false;
+    }
+
+    /**
+     * Adds a removeAll request to the remote cache.
+     * <p>
+     * @throws IOException
+     */
+    @Override
+    public void removeAll()
+        throws IOException
+    {
+        try
+        {
+            cacheEventQueue.addRemoveAllEvent();
+        }
+        catch ( IOException e )
+        {
+            log.error( "Problem adding RemoveAllEvent to queue.", e );
+            cacheEventQueue.destroy();
+            throw e;
+        }
+    }
+
+    /** Adds a dispose request to the remote cache. */
+    @Override
+    public void dispose()
+    {
+        try
+        {
+            cacheEventQueue.addDisposeEvent();
+        }
+        catch ( IOException e )
+        {
+            log.error( "Problem adding DisposeEvent to queue.", e );
+            cacheEventQueue.destroy();
+        }
+    }
+
+    /**
+     * No remote invocation.
+     * <p>
+     * @return The size value
+     */
+    @Override
+    public int getSize()
+    {
+        return remoteCacheClient.getSize();
+    }
+
+    /**
+     * No remote invocation.
+     * <p>
+     * @return The cacheType value
+     */
+    @Override
+    public CacheType getCacheType()
+    {
+        return CacheType.REMOTE_CACHE;
+    }
+
+    /**
+     * Returns the asyn cache status. An error status indicates either the remote connection is not
+     * available, or the asyn queue has been unexpectedly destroyed. No remote invocation.
+     * <p>
+     * @return The status value
+     */
+    @Override
+    public CacheStatus getStatus()
+    {
+        return cacheEventQueue.isWorking() ? remoteCacheClient.getStatus() : CacheStatus.ERROR;
+    }
+
+    /**
+     * Gets the cacheName attribute of the RemoteCacheNoWait object
+     * <p>
+     * @return The cacheName value
+     */
+    @Override
+    public String getCacheName()
+    {
+        return remoteCacheClient.getCacheName();
+    }
+
+    /**
+     * Replaces the remote cache service handle with the given handle and reset the event queue by
+     * starting up a new instance.
+     * <p>
+     * @param remote
+     */
+    public void fixCache( ICacheServiceNonLocal<?, ?> remote )
+    {
+        remoteCacheClient.fixCache( remote );
+        resetEventQ();
+    }
+
+    /**
+     * Resets the event q by first destroying the existing one and starting up new one.
+     * <p>
+     * There may be no good reason to kill the existing queue. We will sometimes need to set a new
+     * listener id, so we should create a new queue. We should let the old queue drain. If we were
+     * Connected to the failover, it would be best to finish sending items.
+     */
+    public void resetEventQ()
+    {
+        ICacheEventQueue<K, V> previousQueue = cacheEventQueue;
+
+        this.cacheEventQueue = createCacheEventQueue(this.remoteCacheClient);
+
+        if ( previousQueue.isWorking() )
+        {
+            // we don't expect anything, it would have all gone to the zombie
+            log.info( "resetEventQ, previous queue has [{0}] items queued up.",
+                    () -> previousQueue.size() );
+            previousQueue.destroy();
+        }
+    }
+
+    /**
+     * This is temporary. It allows the manager to get the lister.
+     * <p>
+     * @return the instance of the remote cache client used by this object
+     */
+    protected IRemoteCacheClient<K, V> getRemoteCache()
+    {
+        return remoteCacheClient;
+    }
+
+    /**
+     * @return Returns the AuxiliaryCacheAttributes.
+     */
+    @Override
+    public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
+    {
+        return remoteCacheClient.getAuxiliaryCacheAttributes();
+    }
+
+    /**
+     * This is for testing only. It allows you to take a look at the event queue.
+     * <p>
+     * @return ICacheEventQueue
+     */
+    protected ICacheEventQueue<K, V> getCacheEventQueue()
+    {
+        return this.cacheEventQueue;
+    }
+
+    /**
+     * Returns the stats and the cache.toString().
+     * <p>
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString()
+    {
+        return getStats() + "\n" + remoteCacheClient.toString();
+    }
+
+    /**
+     * Returns the statistics in String form.
+     * <p>
+     * @return String
+     */
+    @Override
+    public String getStats()
+    {
+        return getStatistics().toString();
+    }
+
+    /**
+     * @return statistics about this communication
+     */
+    @Override
+    public IStats getStatistics()
+    {
+        IStats stats = new Stats();
+        stats.setTypeName( "Remote Cache No Wait" );
+
+        ArrayList<IStatElement<?>> elems = new ArrayList<>();
+
+        elems.add(new StatElement<>( "Status", getStatus() ) );
+
+        // get the stats from the cache queue too
+        IStats cStats = this.remoteCacheClient.getStatistics();
+        if ( cStats != null )
+        {
+            elems.addAll(cStats.getStatElements());
+        }
+
+        // get the stats from the event queue too
+        IStats eqStats = this.cacheEventQueue.getStatistics();
+        elems.addAll(eqStats.getStatElements());
+
+        elems.add(new StatElement<>( "Get Count", Integer.valueOf(this.getCount) ) );
+        elems.add(new StatElement<>( "GetMatching Count", Integer.valueOf(this.getMatchingCount) ) );
+        elems.add(new StatElement<>( "GetMultiple Count", Integer.valueOf(this.getMultipleCount) ) );
+        elems.add(new StatElement<>( "Remove Count", Integer.valueOf(this.removeCount) ) );
+        elems.add(new StatElement<>( "Put Count", Integer.valueOf(this.putCount) ) );
+
+        stats.setStatElements( elems );
+
+        return stats;
+    }
+
+    /**
+     * this won't be called since we don't do ICache logging here.
+     * <p>
+     * @return String
+     */
+    @Override
+    public String getEventLoggingExtraInfo()
+    {
+        return "Remote Cache No Wait";
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheNoWaitFacade.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheNoWaitFacade.java
new file mode 100644
index 0000000..0ec1615
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheNoWaitFacade.java
@@ -0,0 +1,101 @@
+package org.apache.commons.jcs3.auxiliary.remote;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.List;
+
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.remote.server.behavior.RemoteType;
+import org.apache.commons.jcs3.engine.CacheStatus;
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * Used to provide access to multiple services under nowait protection. Factory should construct
+ * NoWaitFacade to give to the composite cache out of caches it constructs from the varies manager
+ * to lateral services.
+ * <p>
+ * Typically, we only connect to one remote server per facade. We use a list of one
+ * RemoteCacheNoWait.
+ */
+public class RemoteCacheNoWaitFacade<K, V>
+    extends AbstractRemoteCacheNoWaitFacade<K, V>
+{
+    /** log instance */
+    private static final Log log = LogManager.getLog( RemoteCacheNoWaitFacade.class );
+
+    /** Provide factory instance to RemoteCacheFailoverRunner */
+    private final RemoteCacheFactory cacheFactory;
+
+    /**
+     * Constructs with the given remote cache, and fires events to any listeners.
+     * <p>
+     * @param noWaits
+     * @param rca
+     * @param cacheEventLogger
+     * @param elementSerializer
+     * @param cacheFactory
+     */
+    public RemoteCacheNoWaitFacade( List<RemoteCacheNoWait<K,V>> noWaits,
+                                    IRemoteCacheAttributes rca,
+                                    ICacheEventLogger cacheEventLogger,
+                                    IElementSerializer elementSerializer,
+                                    RemoteCacheFactory cacheFactory)
+    {
+        super( noWaits, rca, cacheEventLogger, elementSerializer );
+        this.cacheFactory = cacheFactory;
+    }
+
+    /**
+     * Begin the failover process if this is a local cache. Clustered remote caches do not failover.
+     * <p>
+     * @param rcnw The no wait in error.
+     */
+    @Override
+    protected void failover( RemoteCacheNoWait<K, V> rcnw )
+    {
+        log.debug( "in failover for {0}", rcnw );
+
+        if ( getAuxiliaryCacheAttributes().getRemoteType() == RemoteType.LOCAL )
+        {
+            if ( rcnw.getStatus() == CacheStatus.ERROR )
+            {
+                // start failover, primary recovery process
+                RemoteCacheFailoverRunner<K, V> runner = new RemoteCacheFailoverRunner<>( this, this.cacheFactory );
+                runner.setDaemon( true );
+                runner.start();
+                runner.notifyError();
+
+                if ( getCacheEventLogger() != null )
+                {
+                    getCacheEventLogger().logApplicationEvent( "RemoteCacheNoWaitFacade", "InitiatedFailover",
+                                                               rcnw + " was in error." );
+                }
+            }
+            else
+            {
+                log.info( "The noWait is not in error" );
+            }
+        }
+    }
+
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteLocation.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteLocation.java
new file mode 100644
index 0000000..ec2e19e
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteLocation.java
@@ -0,0 +1,144 @@
+package org.apache.commons.jcs3.auxiliary.remote;

+

+import java.util.regex.Matcher;

+import java.util.regex.Pattern;

+

+import org.apache.commons.jcs3.log.Log;

+import org.apache.commons.jcs3.log.LogManager;

+

+/*

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements.  See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership.  The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License.  You may obtain a copy of the License at

+ *

+ *   http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing,

+ * software distributed under the License is distributed on an

+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

+ * KIND, either express or implied.  See the License for the

+ * specific language governing permissions and limitations

+ * under the License.

+ */

+

+/**

+ * Location of the RMI registry.

+ */

+public final class RemoteLocation

+{

+    /** The logger. */

+    private static final Log log = LogManager.getLog( RemoteLocation.class );

+

+    /** Pattern for parsing server:port */

+    private static final Pattern SERVER_COLON_PORT = Pattern.compile("(\\S+)\\s*:\\s*(\\d+)");

+

+    /** Host name */

+    private final String host;

+

+    /** Port */

+    private final int port;

+

+    /**

+     * Constructor for the Location object

+     * <p>

+     * @param host

+     * @param port

+     */

+    public RemoteLocation( String host, int port )

+    {

+        this.host = host;

+        this.port = port;

+    }

+

+    /**

+     * @return the host

+     */

+    public String getHost()

+    {

+        return host;

+    }

+

+    /**

+     * @return the port

+     */

+    public int getPort()

+    {

+        return port;

+    }

+

+    /**

+     * @param obj

+     * @return true if the host and port are equal

+     */

+    @Override

+    public boolean equals( Object obj )

+    {

+        if ( obj == this )

+        {

+            return true;

+        }

+        if ( obj == null || !( obj instanceof RemoteLocation ) )

+        {

+            return false;

+        }

+        RemoteLocation l = (RemoteLocation) obj;

+        if ( this.host == null )

+        {

+            return l.host == null && port == l.port;

+        }

+        return host.equals( l.host ) && port == l.port;

+    }

+

+    /**

+     * @return int

+     */

+    @Override

+    public int hashCode()

+    {

+        return host == null ? port : host.hashCode() ^ port;

+    }

+

+    /**

+     * @see java.lang.Object#toString()

+     */

+    @Override

+    public String toString()

+    {

+        StringBuilder sb = new StringBuilder();

+        if (this.host != null)

+        {

+            sb.append(this.host);

+        }

+        sb.append(':').append(this.port);

+

+        return sb.toString();

+    }

+

+    /**

+     * Parse remote server and port from the string representation server:port and store them in

+     * a RemoteLocation object

+     *

+     * @param server the input string

+     * @return the remote location object

+     */

+    public static RemoteLocation parseServerAndPort(final String server)

+    {

+        Matcher match = SERVER_COLON_PORT.matcher(server);

+

+        if (match.find() && match.groupCount() == 2)

+        {

+            RemoteLocation location = new RemoteLocation( match.group(1), Integer.parseInt( match.group(2) ) );

+            return location;

+        }

+        else

+        {

+            log.error("Invalid server descriptor: {0}", server);

+        }

+

+        return null;

+    }

+}
\ No newline at end of file
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteUtils.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteUtils.java
new file mode 100644
index 0000000..427992d
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/RemoteUtils.java
@@ -0,0 +1,264 @@
+package org.apache.commons.jcs3.auxiliary.remote;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URL;
+import java.rmi.RemoteException;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
+import java.rmi.server.RMISocketFactory;
+import java.util.Properties;
+
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * This class provides some basic utilities for doing things such as starting
+ * the registry properly.
+ */
+public class RemoteUtils
+{
+    /** The logger. */
+    private static final Log log = LogManager.getLog(RemoteUtils.class);
+
+    /** No instances please. */
+    private RemoteUtils()
+    {
+        super();
+    }
+
+    /**
+     * Creates and exports a registry on the specified port of the local host.
+     * <p>
+     *
+     * @param port
+     * @return the registry
+     */
+    public static Registry createRegistry(int port)
+    {
+        Registry registry = null;
+
+        // if ( log.isInfoEnabled() )
+        // {
+        // log.info( "createRegistry> Setting security manager" );
+        // }
+        //
+        // System.setSecurityManager( new RMISecurityManager() );
+
+        if (port < 1024)
+        {
+            log.warn("createRegistry> Port chosen was less than 1024, will use default [{0}] instead.",
+                    Registry.REGISTRY_PORT);
+            port = Registry.REGISTRY_PORT;
+        }
+
+        try
+        {
+            registry = LocateRegistry.createRegistry(port);
+            log.info("createRegistry> Created the registry on port {0}", port);
+        }
+        catch (RemoteException e)
+        {
+            log.warn("createRegistry> Problem creating registry. It may already be started.",
+                    e);
+        }
+        catch (Throwable t)
+        {
+            log.error("createRegistry> Problem creating registry.", t);
+        }
+
+        if (registry == null)
+        {
+            try
+            {
+                registry = LocateRegistry.getRegistry(port);
+            }
+            catch (RemoteException e)
+            {
+                log.error("createRegistry> Problem getting a registry reference.", e);
+            }
+        }
+
+        return registry;
+    }
+
+    /**
+     * Loads properties for the named props file.
+     * First tries class path, then file, then URL
+     * <p>
+     *
+     * @param propFile
+     * @return The properties object for the file
+     * @throws IOException
+     */
+    public static Properties loadProps(String propFile)
+            throws IOException
+    {
+        InputStream is = RemoteUtils.class.getResourceAsStream(propFile);
+
+        if (null == is) // not found in class path
+        {
+            // Try root of class path
+            if (propFile != null && !propFile.startsWith("/"))
+            {
+                is = RemoteUtils.class.getResourceAsStream("/" + propFile);
+            }
+        }
+
+        if (null == is) // not found in class path
+        {
+            if (new File(propFile).exists())
+            {
+                // file found
+                is = new FileInputStream(propFile);
+            }
+            else
+            {
+                // try URL
+                is = new URL(propFile).openStream();
+            }
+        }
+
+        Properties props = new Properties();
+        try
+        {
+            props.load(is);
+            log.debug("props.size={0}", () -> props.size());
+
+            if (log.isTraceEnabled())
+            {
+                StringBuilder buf = new StringBuilder();
+                props.forEach((key, value)
+                        -> buf.append('\n').append(key).append(" = ").append(value));
+                log.trace(buf.toString());
+            }
+
+        }
+        catch (IOException ex)
+        {
+            log.error("Error loading remote properties, for file name "
+                    + "[{0}]", propFile, ex);
+        }
+        finally
+        {
+            if (is != null)
+            {
+                is.close();
+            }
+        }
+        return props;
+    }
+
+    /**
+     * Configure a custom socket factory to set the timeout value. This sets the
+     * global socket factory. It's used only if a custom factory is not
+     * configured for the specific object.
+     * <p>
+     *
+     * @param timeoutMillis
+     */
+    public static void configureGlobalCustomSocketFactory(final int timeoutMillis)
+    {
+        try
+        {
+            // Don't set a socket factory if the setting is -1
+            if (timeoutMillis > 0)
+            {
+                log.info("RmiSocketFactoryTimeoutMillis [{0}]. "
+                    + " Configuring a custom socket factory.", timeoutMillis);
+
+                // use this socket factory to add a timeout.
+                RMISocketFactory.setSocketFactory(new RMISocketFactory()
+                {
+                    @Override
+                    public Socket createSocket(String host, int port)
+                            throws IOException
+                    {
+                        Socket socket = new Socket();
+                        socket.setSoTimeout(timeoutMillis);
+                        socket.setSoLinger(false, 0);
+                        socket.connect(new InetSocketAddress(host, port), timeoutMillis);
+                        return socket;
+                    }
+
+                    @Override
+                    public ServerSocket createServerSocket(int port)
+                            throws IOException
+                    {
+                        return new ServerSocket(port);
+                    }
+                });
+            }
+        }
+        catch (IOException e)
+        {
+            // Only try to do it once. Otherwise we
+            // Generate errors for each region on construction.
+            RMISocketFactory factoryInUse = RMISocketFactory.getSocketFactory();
+            if (factoryInUse != null && !factoryInUse.getClass().getName().startsWith("org.apache.commons.jcs3"))
+            {
+                log.info("Could not create new custom socket factory. {0} Factory in use = {1}",
+                        () -> e.getMessage(), () -> RMISocketFactory.getSocketFactory());
+            }
+        }
+    }
+
+    /**
+     * Get the naming url used for RMI registration
+     *
+     * @param location
+     *            the remote location
+     * @param serviceName
+     *            the remote service name
+     * @return the URL for RMI lookup
+     */
+    public static String getNamingURL(final RemoteLocation location, final String serviceName)
+    {
+        return getNamingURL(location.getHost(), location.getPort(), serviceName);
+    }
+
+    /**
+     * Get the naming url used for RMI registration
+     *
+     * @param registryHost
+     *            the remote host
+     * @param registryPort
+     *            the remote port
+     * @param serviceName
+     *            the remote service name
+     * @return the URL for RMI lookup
+     */
+    public static String getNamingURL(final String registryHost, final int registryPort, final String serviceName)
+    {
+        if (registryHost.contains(":"))
+        { // TODO improve this check? See also JCS-133
+            return "//[" + registryHost.replaceFirst("%", "%25") + "]:" + registryPort + "/" + serviceName;
+        }
+        final String registryURL = "//" + registryHost + ":" + registryPort + "/" + serviceName;
+        return registryURL;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/behavior/ICommonRemoteCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/behavior/ICommonRemoteCacheAttributes.java
new file mode 100644
index 0000000..be29370
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/behavior/ICommonRemoteCacheAttributes.java
@@ -0,0 +1,172 @@
+package org.apache.commons.jcs3.auxiliary.remote.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.remote.RemoteLocation;
+import org.apache.commons.jcs3.auxiliary.remote.server.behavior.RemoteType;
+
+/**
+ * This specifies what a remote cache configuration object should look like.
+ */
+public interface ICommonRemoteCacheAttributes
+    extends AuxiliaryCacheAttributes
+{
+    /** The default timeout for the custom RMI socket factory */
+    int DEFAULT_RMI_SOCKET_FACTORY_TIMEOUT_MILLIS = 10000;
+
+    /**
+     * Gets the remoteTypeName attribute of the IRemoteCacheAttributes object
+     * <p>
+     * @return The remoteTypeName value
+     */
+    String getRemoteTypeName();
+
+    /**
+     * Sets the remoteTypeName attribute of the IRemoteCacheAttributes object
+     * <p>
+     * @param s The new remoteTypeName value
+     */
+    void setRemoteTypeName( String s );
+
+    /**
+     * Gets the remoteType attribute of the IRemoteCacheAttributes object
+     * <p>
+     * @return The remoteType value
+     */
+    RemoteType getRemoteType();
+
+    /**
+     * Sets the remoteType attribute of the IRemoteCacheAttributes object
+     * <p>
+     * @param p The new remoteType value
+     */
+    void setRemoteType( RemoteType p );
+
+    /**
+     * Gets the remoteServiceName attribute of the IRemoteCacheAttributes object
+     * <p>
+     * @return The remoteServiceName value
+     */
+    String getRemoteServiceName();
+
+    /**
+     * Sets the remoteServiceName attribute of the IRemoteCacheAttributes object
+     * <p>
+     * @param s The new remoteServiceName value
+     */
+    void setRemoteServiceName( String s );
+
+    /**
+     * Sets the location attribute of the RemoteCacheAttributes object.
+     * <p>
+     * @param location The new location value
+     */
+    void setRemoteLocation( RemoteLocation location );
+
+    /**
+     * Sets the location attribute of the RemoteCacheAttributes object.
+     * <p>
+     * @param host The new remoteHost value
+     * @param port The new remotePort value
+     */
+    void setRemoteLocation( String host, int port );
+
+    /**
+     * Gets the location attribute of the RemoteCacheAttributes object.
+     * <p>
+     * @return The remote location value
+     */
+    public RemoteLocation getRemoteLocation();
+
+    /**
+     * Gets the clusterServers attribute of the IRemoteCacheAttributes object
+     * <p>
+     * @return The clusterServers value
+     */
+    String getClusterServers();
+
+    /**
+     * Sets the clusterServers attribute of the IRemoteCacheAttributes object
+     * <p>
+     * @param s The new clusterServers value
+     */
+    void setClusterServers( String s );
+
+    /**
+     * Gets the removeUponRemotePut attribute of the IRemoteCacheAttributes object
+     * <p>
+     * @return The removeUponRemotePut value
+     */
+    boolean getRemoveUponRemotePut();
+
+    /**
+     * Sets the removeUponRemotePut attribute of the IRemoteCacheAttributes object
+     * <p>
+     * @param r The new removeUponRemotePut value
+     */
+    void setRemoveUponRemotePut( boolean r );
+
+    /**
+     * Gets the getOnly attribute of the IRemoteCacheAttributes object
+     * <p>
+     * @return The getOnly value
+     */
+    boolean getGetOnly();
+
+    /**
+     * Sets the getOnly attribute of the IRemoteCacheAttributes object
+     * <p>
+     * @param r The new getOnly value
+     */
+    void setGetOnly( boolean r );
+
+    /**
+     * Should cluster updates be propagated to the locals
+     * <p>
+     * @return The localClusterConsistency value
+     */
+    boolean isLocalClusterConsistency();
+
+    /**
+     * Should cluster updates be propagated to the locals
+     * <p>
+     * @param r The new localClusterConsistency value
+     */
+    void setLocalClusterConsistency( boolean r );
+
+    /**
+     * This sets a general timeout on the rmi socket factory. By default the socket factory will
+     * block forever.
+     * <p>
+     * We have a default setting. The default rmi behavior should never be used.
+     * <p>
+     * @return int milliseconds
+     */
+    int getRmiSocketFactoryTimeoutMillis();
+
+    /**
+     * This sets a general timeout on the RMI socket factory. By default the socket factory will
+     * block forever.
+     * <p>
+     * @param rmiSocketFactoryTimeoutMillis
+     */
+    void setRmiSocketFactoryTimeoutMillis( int rmiSocketFactoryTimeoutMillis );
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/behavior/IRemoteCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/behavior/IRemoteCacheAttributes.java
new file mode 100644
index 0000000..494c630
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/behavior/IRemoteCacheAttributes.java
@@ -0,0 +1,182 @@
+package org.apache.commons.jcs3.auxiliary.remote.behavior;
+
+import java.util.List;
+
+import org.apache.commons.jcs3.auxiliary.remote.RemoteLocation;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * This specifies what a remote cache configuration object should look like.
+ */
+public interface IRemoteCacheAttributes
+    extends ICommonRemoteCacheAttributes
+{
+    /**
+     * If RECEIVE is false then the remote cache will not register a listener with the remote
+     * server. This allows you to configure a remote server as a repository from which you can get
+     * and to which you put, but from which you do not receive any notifications. That is, you will
+     * not receive updates or removes.
+     * <p>
+     * If you set this option to false, you should set your local memory size to 0.
+     */
+    boolean DEFAULT_RECEIVE = true;
+
+    /**
+     * The number of elements the zombie queue will hold. This queue is used to store events if we
+     * loose our connection with the server.
+     */
+    int DEFAULT_ZOMBIE_QUEUE_MAX_SIZE = 1000;
+
+    /**
+     * Gets the failoverIndex attribute of the IRemoteCacheAttributes object.
+     * <p>
+     * This specifies which server in the list we are listening to if the number is greater than 0
+     * we will try to move to 0 position the primary is added as position 1 if it is present
+     * <p>
+     * @return The failoverIndex value
+     */
+    int getFailoverIndex();
+
+    /**
+     * Sets the failoverIndex attribute of the IRemoteCacheAttributes object
+     * <p>
+     * @param p The new failoverIndex value
+     */
+    void setFailoverIndex( int p );
+
+    /**
+     * Gets the failovers attribute of the IRemoteCacheAttributes object
+     * <p>
+     * @return The failovers value
+     */
+    List<RemoteLocation> getFailovers();
+
+    /**
+     * Sets the failovers attribute of the IRemoteCacheAttributes object
+     * <p>
+     * @param failovers The new failovers value
+     */
+    void setFailovers( List<RemoteLocation> failovers );
+
+    /**
+     * Gets the localPort attribute of the IRemoteCacheAttributes object
+     * <p>
+     * @return The localPort value
+     */
+    int getLocalPort();
+
+    /**
+     * Sets the localPort attribute of the IRemoteCacheAttributes object
+     * <p>
+     * @param p The new localPort value
+     */
+    void setLocalPort( int p );
+
+    /**
+     * Gets the failoverServers attribute of the IRemoteCacheAttributes object
+     * <p>
+     * @return The failoverServers value
+     */
+    String getFailoverServers();
+
+    /**
+     * Sets the failoverServers attribute of the IRemoteCacheAttributes object
+     * <p>
+     * @param s The new failoverServers value
+     */
+    void setFailoverServers( String s );
+
+    /**
+     * The thread pool the remote cache should use. At first this will only be for gets.
+     * <p>
+     * The default name is "remote_cache_client"
+     * <p>
+     * @return the name of the pool
+     */
+    String getThreadPoolName();
+
+    /**
+     * Set the name of the pool to use. Pools should be defined in the cache.ccf.
+     * <p>
+     * @param name
+     */
+    void setThreadPoolName( String name );
+
+    /**
+     * -1 and 0 mean no timeout, this is the default if the timeout is -1 or 0, no threadpool will
+     * be used.
+     * <p>
+     * @return the time in millis
+     */
+    int getGetTimeoutMillis();
+
+    /**
+     * -1 means no timeout, this is the default if the timeout is -1 or 0, no threadpool will be
+     * used. If the timeout is greater than 0 a threadpool will be used for get requests.
+     * <p>
+     * @param millis
+     */
+    void setGetTimeoutMillis( int millis );
+
+    /**
+     * By default this option is true. If you set it to false, you will not receive updates or
+     * removes from the remote server.
+     * <p>
+     * @param receive
+     */
+    void setReceive( boolean receive );
+
+    /**
+     * If RECEIVE is false then the remote cache will not register a listener with the remote
+     * server. This allows you to configure a remote server as a repository from which you can get
+     * and to which you put, but from which you do not receive any notifications. That is, you will
+     * not receive updates or removes.
+     * <p>
+     * If you set this option to false, you should set your local memory size to 0.
+     * <p>
+     * The remote cache manager uses this value to decide whether or not to register a listener.
+     * <p>
+     * It makes no sense to configure a cluster remote cache to no receive.
+     * <p>
+     * Since a non-receiving remote cache client will not register a listener, it will not have a
+     * listener id assigned from the server. As such the remote server cannot determine if it is a
+     * cluster or a normal client. It will assume that it is a normal client.
+     * <p>
+     * @return the receive value.
+     */
+    boolean isReceive();
+
+    /**
+     * The number of elements the zombie queue will hold. This queue is used to store events if we
+     * loose our connection with the server.
+     * <p>
+     * @param zombieQueueMaxSize The zombieQueueMaxSize to set.
+     */
+    void setZombieQueueMaxSize( int zombieQueueMaxSize );
+
+    /**
+     * The number of elements the zombie queue will hold. This queue is used to store events if we
+     * loose our connection with the server.
+     * <p>
+     * @return Returns the zombieQueueMaxSize.
+     */
+    int getZombieQueueMaxSize();
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/behavior/IRemoteCacheClient.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/behavior/IRemoteCacheClient.java
new file mode 100644
index 0000000..013e9a9
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/behavior/IRemoteCacheClient.java
@@ -0,0 +1,61 @@
+package org.apache.commons.jcs3.auxiliary.remote.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCache;
+import org.apache.commons.jcs3.engine.behavior.ICacheServiceNonLocal;
+
+/**
+ * This defines the behavior expected of a remote cache client. This extends Auxiliary cache which
+ * in turn extends ICache.
+ * <p>
+ * I'd like generalize this a bit.
+ * <p>
+ * @author Aaron Smuts
+ */
+public interface IRemoteCacheClient<K, V>
+    extends AuxiliaryCache<K, V>
+{
+    /**
+     * Replaces the current remote cache service handle with the given handle. If the current remote
+     * is a Zombie, the propagate the events that may be queued to the restored service.
+     * <p>
+     * @param remote ICacheServiceNonLocal -- the remote server or proxy to the remote server
+     */
+    void fixCache( ICacheServiceNonLocal<?, ?> remote );
+
+    /**
+     * Gets the listenerId attribute of the RemoteCacheListener object.
+     * <p>
+     * All requests to the remote cache must include a listener id. This allows the server to avoid
+     * sending updates the the listener associated with this client.
+     * <p>
+     * @return The listenerId value
+     */
+    long getListenerId();
+
+    /**
+     * This returns the listener associated with this remote cache. TODO we should try to get this
+     * out of the interface.
+     * <p>
+     * @return IRemoteCacheListener
+     */
+    IRemoteCacheListener<K, V> getListener();
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/behavior/IRemoteCacheConstants.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/behavior/IRemoteCacheConstants.java
new file mode 100644
index 0000000..515db6a
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/behavior/IRemoteCacheConstants.java
@@ -0,0 +1,70 @@
+package org.apache.commons.jcs3.auxiliary.remote.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.engine.behavior.ICacheServiceNonLocal;
+
+
+/**
+ * This holds constants that are used by the remote cache.
+ */
+public interface IRemoteCacheConstants
+{
+    /** Mapping to props file value */
+    String REMOTE_CACHE_SERVICE_VAL = ICacheServiceNonLocal.class.getName();
+
+    /** The prefix for cache server config. */
+    String CACHE_SERVER_PREFIX = "jcs.remotecache";
+
+    /**
+     * I'm trying to migrate everything to use this prefix. All those below will be replaced. Any of
+     * the RemoteCacheServerAttributes can be configured this way.
+     */
+    String CACHE_SERVER_ATTRIBUTES_PROPERTY_PREFIX = CACHE_SERVER_PREFIX + ".serverattributes";
+
+    /**
+     * This is the name of the class that will be used for an object specific socket factory.
+     */
+    String CUSTOM_RMI_SOCKET_FACTORY_PROPERTY_PREFIX = CACHE_SERVER_PREFIX + ".customrmisocketfactory";
+
+    /** Property prefix, should be jcs.remote but this would break existing config. */
+    String PROPERTY_PREFIX = "remote";
+
+    /** Mapping to props file value */
+    String SOCKET_TIMEOUT_MILLIS = PROPERTY_PREFIX + ".cache.rmiSocketFactoryTimeoutMillis";
+
+    /** Mapping to props file value */
+    String REMOTE_CACHE_SERVICE_NAME = PROPERTY_PREFIX + ".cache.service.name";
+
+    /** Mapping to props file value */
+    String TOMCAT_XML = PROPERTY_PREFIX + ".tomcat.xml";
+
+    /** Mapping to props file value */
+    String TOMCAT_ON = PROPERTY_PREFIX + ".tomcat.on";
+
+    /** Mapping to props file value */
+    String REMOTE_CACHE_SERVICE_PORT = PROPERTY_PREFIX + ".cache.service.port";
+
+    /** Mapping to props file value */
+    String REMOTE_LOCAL_CLUSTER_CONSISTENCY = PROPERTY_PREFIX + ".cluster.LocalClusterConsistency";
+
+    /** Mapping to props file value */
+    String REMOTE_ALLOW_CLUSTER_GET = PROPERTY_PREFIX + ".cluster.AllowClusterGet";
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/behavior/IRemoteCacheDispatcher.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/behavior/IRemoteCacheDispatcher.java
new file mode 100644
index 0000000..0f08947
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/behavior/IRemoteCacheDispatcher.java
@@ -0,0 +1,46 @@
+package org.apache.commons.jcs3.auxiliary.remote.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+
+import org.apache.commons.jcs3.auxiliary.remote.value.RemoteCacheRequest;
+import org.apache.commons.jcs3.auxiliary.remote.value.RemoteCacheResponse;
+
+/**
+ * In the future, this can be used as a generic dispatcher abstraction.
+ * <p>
+ * At the time of creation, only the http remote cache uses it. The RMI remote could be converted to
+ * use it as well.
+ */
+public interface IRemoteCacheDispatcher
+{
+    /**
+     * All requests will go through this method. The dispatcher implementation will send the request
+     * remotely.
+     * <p>
+     * @param remoteCacheRequest
+     * @return RemoteCacheResponse
+     * @throws IOException
+     */
+    <K, V, T>
+        RemoteCacheResponse<T> dispatchRequest( RemoteCacheRequest<K, V> remoteCacheRequest )
+            throws IOException;
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/behavior/IRemoteCacheListener.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/behavior/IRemoteCacheListener.java
new file mode 100644
index 0000000..fd5673b
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/behavior/IRemoteCacheListener.java
@@ -0,0 +1,81 @@
+package org.apache.commons.jcs3.auxiliary.remote.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.rmi.Remote;
+
+import org.apache.commons.jcs3.auxiliary.remote.server.behavior.RemoteType;
+import org.apache.commons.jcs3.engine.behavior.ICacheListener;
+
+/**
+ * Listens for remote cache event notification ( rmi callback ).
+ */
+public interface IRemoteCacheListener<K, V>
+    extends ICacheListener<K, V>, Remote
+{
+    /**
+     * Get the id to be used by this manager.
+     * <p>
+     * @return long
+     * @throws IOException
+     */
+    @Override
+    long getListenerId()
+        throws IOException;
+
+    /**
+     * Set the id to be used by this manager. The remote cache server identifies clients by this id.
+     * The value will be set by the server through the remote cache listener.
+     * <p>
+     * @param id
+     * @throws IOException
+     */
+    @Override
+    void setListenerId( long id )
+        throws IOException;
+
+    /**
+     * Gets the remoteType attribute of the IRemoteCacheListener object
+     * <p>
+     * @return The remoteType value
+     * @throws IOException
+     */
+    RemoteType getRemoteType()
+        throws IOException;
+
+    /**
+     * This is for debugging. It allows the remote cache server to log the address of any listeners
+     * that register.
+     * <p>
+     * @return the local host address.
+     * @throws IOException
+     */
+    String getLocalHostAddress()
+        throws IOException;
+
+    /**
+     * Deregisters itself.
+     * <p>
+     * @throws IOException
+     */
+    void dispose()
+        throws IOException;
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/behavior/IRemoteHttpCacheConstants.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/behavior/IRemoteHttpCacheConstants.java
new file mode 100644
index 0000000..281554a
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/behavior/IRemoteHttpCacheConstants.java
@@ -0,0 +1,31 @@
+package org.apache.commons.jcs3.auxiliary.remote.http.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/** Constants used throughout the HTTP remote cache. */
+public interface IRemoteHttpCacheConstants
+{
+    /** The prefix for cache server config. */
+    String HTTP_CACHE_SERVER_PREFIX = "jcs.remotehttpcache";
+
+    /** All of the RemoteHttpCacheServiceAttributes can be configured this way. */
+    String HTTP_CACHE_SERVER_ATTRIBUTES_PROPERTY_PREFIX = HTTP_CACHE_SERVER_PREFIX
+        + ".serverattributes";
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/client/AbstractHttpClient.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/client/AbstractHttpClient.java
new file mode 100644
index 0000000..832a75b
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/client/AbstractHttpClient.java
@@ -0,0 +1,154 @@
+package org.apache.commons.jcs3.auxiliary.remote.http.client;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpVersion;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.config.CookieSpecs;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.methods.RequestBuilder;
+import org.apache.http.impl.client.HttpClientBuilder;
+
+/**
+ * This class simply configures the http multithreaded connection manager.
+ * <p>
+ * This is abstract because it can do anything. Child classes can overwrite whatever they want.
+ */
+public abstract class AbstractHttpClient
+{
+    /** The client */
+    private final HttpClient httpClient;
+
+    /** The protocol version */
+    private HttpVersion httpVersion;
+
+    /** Configuration settings. */
+    private final RemoteHttpCacheAttributes remoteHttpCacheAttributes;
+
+    /** The Logger. */
+    private static final Log log = LogManager.getLog( AbstractHttpClient.class );
+
+    /**
+     * Sets the default Properties File and Heading, and creates the HttpClient and connection
+     * manager.
+     * <p>
+     * @param remoteHttpCacheAttributes
+     */
+    public AbstractHttpClient( RemoteHttpCacheAttributes remoteHttpCacheAttributes )
+    {
+        this.remoteHttpCacheAttributes = remoteHttpCacheAttributes;
+
+        String httpVersion = getRemoteHttpCacheAttributes().getHttpVersion();
+        if ( "1.1".equals( httpVersion ) )
+        {
+            this.httpVersion = HttpVersion.HTTP_1_1;
+        }
+        else if ( "1.0".equals( httpVersion ) )
+        {
+            this.httpVersion = HttpVersion.HTTP_1_0;
+        }
+        else
+        {
+            log.warn( "Unrecognized value for 'httpVersion': [{0}], defaulting to 1.1",
+                    httpVersion );
+            this.httpVersion = HttpVersion.HTTP_1_1;
+        }
+
+        HttpClientBuilder builder = HttpClientBuilder.create();
+        configureClient(builder);
+        this.httpClient = builder.build();
+    }
+
+    /**
+     * Configures the http client.
+     *
+     * @param builder client builder to configure
+     */
+    protected void configureClient(HttpClientBuilder builder)
+    {
+        if ( getRemoteHttpCacheAttributes().getMaxConnectionsPerHost() > 0 )
+        {
+            builder.setMaxConnTotal(getRemoteHttpCacheAttributes().getMaxConnectionsPerHost());
+            builder.setMaxConnPerRoute(getRemoteHttpCacheAttributes().getMaxConnectionsPerHost());
+        }
+
+        builder.setDefaultRequestConfig(RequestConfig.custom()
+                .setConnectTimeout(getRemoteHttpCacheAttributes().getConnectionTimeoutMillis())
+                .setSocketTimeout(getRemoteHttpCacheAttributes().getSocketTimeoutMillis())
+                // By default we instruct HttpClient to ignore cookies.
+                .setCookieSpec(CookieSpecs.IGNORE_COOKIES)
+                .build());
+    }
+
+    /**
+     * Execute the web service call
+     * <p>
+     * @param builder builder for the post request
+     *
+     * @return the call response
+     *
+     * @throws IOException on i/o error
+     */
+    protected final HttpResponse doWebserviceCall( RequestBuilder builder )
+        throws IOException
+    {
+        preProcessWebserviceCall( builder.setVersion(httpVersion) );
+        HttpUriRequest request = builder.build();
+        HttpResponse httpResponse = this.httpClient.execute( request );
+        postProcessWebserviceCall( request, httpResponse );
+
+        return httpResponse;
+    }
+
+    /**
+     * Called before the execute call on the client.
+     * <p>
+     * @param requestBuilder http method request builder
+     *
+     * @throws IOException
+     */
+    protected abstract void preProcessWebserviceCall( RequestBuilder requestBuilder )
+        throws IOException;
+
+    /**
+     * Called after the execute call on the client.
+     * <p>
+     * @param request http request
+     * @param httpState result of execution
+     *
+     * @throws IOException
+     */
+    protected abstract void postProcessWebserviceCall( HttpUriRequest request, HttpResponse httpState )
+        throws IOException;
+
+    /**
+     * @return the remoteHttpCacheAttributes
+     */
+    protected RemoteHttpCacheAttributes getRemoteHttpCacheAttributes()
+    {
+        return remoteHttpCacheAttributes;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/client/RemoteHttpCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/client/RemoteHttpCache.java
new file mode 100644
index 0000000..340be23
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/client/RemoteHttpCache.java
@@ -0,0 +1,113 @@
+package org.apache.commons.jcs3.auxiliary.remote.http.client;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+
+import org.apache.commons.jcs3.auxiliary.remote.AbstractRemoteAuxiliaryCache;
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheListener;
+import org.apache.commons.jcs3.engine.ZombieCacheServiceNonLocal;
+import org.apache.commons.jcs3.engine.behavior.ICacheServiceNonLocal;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * This uses an http client as the service.
+ */
+public class RemoteHttpCache<K, V>
+    extends AbstractRemoteAuxiliaryCache<K, V>
+{
+    /** The logger. */
+    private static final Log log = LogManager.getLog( RemoteHttpCache.class );
+
+    /** for error notifications */
+    private final RemoteHttpCacheMonitor monitor;
+
+    /** Keep the child copy here for the restore process. */
+    private final RemoteHttpCacheAttributes remoteHttpCacheAttributes;
+
+    /**
+     * Constructor for the RemoteCache object. This object communicates with a remote cache server.
+     * One of these exists for each region. This also holds a reference to a listener. The same
+     * listener is used for all regions for one remote server. Holding a reference to the listener
+     * allows this object to know the listener id assigned by the remote cache.
+     * <p>
+     * @param remoteHttpCacheAttributes
+     * @param remote
+     * @param listener
+     * @param monitor the cache monitor
+     */
+    public RemoteHttpCache( RemoteHttpCacheAttributes remoteHttpCacheAttributes, ICacheServiceNonLocal<K, V> remote,
+                            IRemoteCacheListener<K, V> listener, RemoteHttpCacheMonitor monitor )
+    {
+        super( remoteHttpCacheAttributes, remote, listener );
+
+        this.remoteHttpCacheAttributes = remoteHttpCacheAttributes;
+        this.monitor = monitor;
+    }
+
+    /**
+     * Nothing right now. This should setup a zombie and initiate recovery.
+     * <p>
+     * @param ex
+     * @param msg
+     * @param eventName
+     * @throws IOException
+     */
+    @Override
+    protected void handleException( Exception ex, String msg, String eventName )
+        throws IOException
+    {
+        // we should not switch if the existing is a zombie.
+        if ( !( getRemoteCacheService() instanceof ZombieCacheServiceNonLocal ) )
+        {
+            String message = "Disabling remote cache due to error: " + msg;
+            logError( cacheName, "", message );
+            log.error( message, ex );
+
+            setRemoteCacheService( new ZombieCacheServiceNonLocal<>( getRemoteCacheAttributes().getZombieQueueMaxSize() ) );
+
+            monitor.notifyError( this );
+        }
+
+        if ( ex instanceof IOException )
+        {
+            throw (IOException) ex;
+        }
+        throw new IOException( ex.getMessage() );
+    }
+
+    /**
+     * @return url of service
+     */
+    @Override
+    public String getEventLoggingExtraInfo()
+    {
+        return null;
+    }
+
+    /**
+     * @return the remoteHttpCacheAttributes
+     */
+    public RemoteHttpCacheAttributes getRemoteHttpCacheAttributes()
+    {
+        return remoteHttpCacheAttributes;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/client/RemoteHttpCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/client/RemoteHttpCacheAttributes.java
new file mode 100644
index 0000000..68d008a
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/client/RemoteHttpCacheAttributes.java
@@ -0,0 +1,228 @@
+package org.apache.commons.jcs3.auxiliary.remote.http.client;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes;
+
+/** Http client specific settings. */
+public class RemoteHttpCacheAttributes
+    extends RemoteCacheAttributes
+{
+    /** Don't change. */
+    private static final long serialVersionUID = -5944327125140505212L;
+
+    /** http verison to use. */
+    private static final String DEFAULT_HTTP_VERSION = "1.1";
+
+    /** The max connections allowed per host */
+    private int maxConnectionsPerHost = 100;
+
+    /** The socket timeout. */
+    private int socketTimeoutMillis = 3000;
+
+    /** The socket connections timeout */
+    private int connectionTimeoutMillis = 5000;
+
+    /** http verison to use. */
+    private String httpVersion = DEFAULT_HTTP_VERSION;
+
+    /** The cache name will be included on the parameters */
+    private boolean includeCacheNameAsParameter = true;
+
+    /** keys and patterns will be included in the parameters */
+    private boolean includeKeysAndPatternsAsParameter = true;
+
+    /** keys and patterns will be included in the parameters */
+    private boolean includeRequestTypeasAsParameter = true;
+
+    /** The complete URL to the service. */
+    private String url;
+
+    /** The default classname for the client.  */
+    public static final String DEFAULT_REMOTE_HTTP_CLIENT_CLASS_NAME = RemoteHttpCacheClient.class.getName();
+
+    /** This allows users to inject their own client implementation. */
+    private String remoteHttpClientClassName = DEFAULT_REMOTE_HTTP_CLIENT_CLASS_NAME;
+
+    /**
+     * @param maxConnectionsPerHost the maxConnectionsPerHost to set
+     */
+    public void setMaxConnectionsPerHost( int maxConnectionsPerHost )
+    {
+        this.maxConnectionsPerHost = maxConnectionsPerHost;
+    }
+
+    /**
+     * @return the maxConnectionsPerHost
+     */
+    public int getMaxConnectionsPerHost()
+    {
+        return maxConnectionsPerHost;
+    }
+
+    /**
+     * @param socketTimeoutMillis the socketTimeoutMillis to set
+     */
+    public void setSocketTimeoutMillis( int socketTimeoutMillis )
+    {
+        this.socketTimeoutMillis = socketTimeoutMillis;
+    }
+
+    /**
+     * @return the socketTimeoutMillis
+     */
+    public int getSocketTimeoutMillis()
+    {
+        return socketTimeoutMillis;
+    }
+
+    /**
+     * @param httpVersion the httpVersion to set
+     */
+    public void setHttpVersion( String httpVersion )
+    {
+        this.httpVersion = httpVersion;
+    }
+
+    /**
+     * @return the httpVersion
+     */
+    public String getHttpVersion()
+    {
+        return httpVersion;
+    }
+
+    /**
+     * @param connectionTimeoutMillis the connectionTimeoutMillis to set
+     */
+    public void setConnectionTimeoutMillis( int connectionTimeoutMillis )
+    {
+        this.connectionTimeoutMillis = connectionTimeoutMillis;
+    }
+
+    /**
+     * @return the connectionTimeoutMillis
+     */
+    public int getConnectionTimeoutMillis()
+    {
+        return connectionTimeoutMillis;
+    }
+
+    /**
+     * @param includeCacheNameInURL the includeCacheNameInURL to set
+     */
+    public void setIncludeCacheNameAsParameter( boolean includeCacheNameInURL )
+    {
+        this.includeCacheNameAsParameter = includeCacheNameInURL;
+    }
+
+    /**
+     * @return the includeCacheNameInURL
+     */
+    public boolean isIncludeCacheNameAsParameter()
+    {
+        return includeCacheNameAsParameter;
+    }
+
+    /**
+     * @param includeKeysAndPatternsInURL the includeKeysAndPatternsInURL to set
+     */
+    public void setIncludeKeysAndPatternsAsParameter( boolean includeKeysAndPatternsInURL )
+    {
+        this.includeKeysAndPatternsAsParameter = includeKeysAndPatternsInURL;
+    }
+
+    /**
+     * @return the includeKeysAndPatternsInURL
+     */
+    public boolean isIncludeKeysAndPatternsAsParameter()
+    {
+        return includeKeysAndPatternsAsParameter;
+    }
+
+    /**
+     * @param includeRequestTypeasAsParameter the includeRequestTypeasAsParameter to set
+     */
+    public void setIncludeRequestTypeasAsParameter( boolean includeRequestTypeasAsParameter )
+    {
+        this.includeRequestTypeasAsParameter = includeRequestTypeasAsParameter;
+    }
+
+    /**
+     * @return the includeRequestTypeasAsParameter
+     */
+    public boolean isIncludeRequestTypeasAsParameter()
+    {
+        return includeRequestTypeasAsParameter;
+    }
+
+    /**
+     * @param url the url to set
+     */
+    public void setUrl( String url )
+    {
+        this.url = url;
+    }
+
+    /**
+     * @return the url
+     */
+    public String getUrl()
+    {
+        return url;
+    }
+
+    /**
+     * @param remoteHttpClientClassName the remoteHttpClientClassName to set
+     */
+    public void setRemoteHttpClientClassName( String remoteHttpClientClassName )
+    {
+        this.remoteHttpClientClassName = remoteHttpClientClassName;
+    }
+
+    /**
+     * @return the remoteHttpClientClassName
+     */
+    public String getRemoteHttpClientClassName()
+    {
+        return remoteHttpClientClassName;
+    }
+
+    /**
+     * @return String details
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        buf.append( "\n RemoteHttpCacheAttributes" );
+        buf.append( "\n maxConnectionsPerHost = [" + getMaxConnectionsPerHost() + "]" );
+        buf.append( "\n socketTimeoutMillis = [" + getSocketTimeoutMillis() + "]" );
+        buf.append( "\n httpVersion = [" + getHttpVersion() + "]" );
+        buf.append( "\n connectionTimeoutMillis = [" + getConnectionTimeoutMillis() + "]" );
+        buf.append( "\n includeCacheNameAsParameter = [" + isIncludeCacheNameAsParameter() + "]" );
+        buf.append( "\n includeKeysAndPatternsAsParameter = [" + isIncludeKeysAndPatternsAsParameter() + "]" );
+        buf.append( "\n includeRequestTypeasAsParameter = [" + isIncludeRequestTypeasAsParameter() + "]" );
+        buf.append( "\n url = [" + getUrl() + "]" );
+        buf.append( "\n remoteHttpClientClassName = [" + getRemoteHttpClientClassName() + "]" );
+        buf.append( super.toString() );
+        return buf.toString();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/client/RemoteHttpCacheClient.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/client/RemoteHttpCacheClient.java
new file mode 100644
index 0000000..444240f
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/client/RemoteHttpCacheClient.java
@@ -0,0 +1,484 @@
+package org.apache.commons.jcs3.auxiliary.remote.http.client;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheDispatcher;
+import org.apache.commons.jcs3.auxiliary.remote.http.client.behavior.IRemoteHttpCacheClient;
+import org.apache.commons.jcs3.auxiliary.remote.util.RemoteCacheRequestFactory;
+import org.apache.commons.jcs3.auxiliary.remote.value.RemoteCacheRequest;
+import org.apache.commons.jcs3.auxiliary.remote.value.RemoteCacheResponse;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/** This is the service used by the remote http auxiliary cache. */
+public class RemoteHttpCacheClient<K, V>
+    implements IRemoteHttpCacheClient<K, V>
+{
+    /** The Logger. */
+    private static final Log log = LogManager.getLog( RemoteHttpCacheClient.class );
+
+    /** The internal client. */
+    private IRemoteCacheDispatcher remoteDispatcher;
+
+    /** The remote attributes */
+    private RemoteHttpCacheAttributes remoteHttpCacheAttributes;
+
+    /** Set to true when initialize is called */
+    private boolean initialized = false;
+
+    /** For factory construction. */
+    public RemoteHttpCacheClient()
+    {
+        // does nothing
+    }
+
+    /**
+     * Constructs a client.
+     * <p>
+     * @param attributes
+     */
+    public RemoteHttpCacheClient( RemoteHttpCacheAttributes attributes )
+    {
+        setRemoteHttpCacheAttributes( attributes );
+        initialize( attributes );
+    }
+
+    /**
+     * The provides an extension point. If you want to extend this and use a special dispatcher,
+     * here is the place to do it.
+     * <p>
+     * @param attributes
+     */
+    @Override
+    public void initialize( RemoteHttpCacheAttributes attributes )
+    {
+        setRemoteDispatcher( new RemoteHttpCacheDispatcher( attributes ) );
+
+        log.info( "Created remote Dispatcher. {0}", () -> getRemoteDispatcher() );
+        setInitialized( true );
+    }
+
+    /**
+     * Create a request, process, extract the payload.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @return ICacheElement
+     * @throws IOException
+     */
+    @Override
+    public ICacheElement<K, V> get( String cacheName, K key )
+        throws IOException
+    {
+        return get( cacheName, key, 0 );
+    }
+
+    /**
+     * Create a request, process, extract the payload.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @param requesterId
+     * @return ICacheElement
+     * @throws IOException
+     */
+    @Override
+    public ICacheElement<K, V> get( String cacheName, K key, long requesterId )
+        throws IOException
+    {
+        if ( !isInitialized() )
+        {
+            String message = "The Remote Http Client is not initialized. Cannot process request.";
+            log.warn( message );
+            throw new IOException( message );
+        }
+        RemoteCacheRequest<K, Serializable> remoteHttpCacheRequest =
+            RemoteCacheRequestFactory.createGetRequest( cacheName, key, requesterId );
+
+        RemoteCacheResponse<ICacheElement<K, V>> remoteHttpCacheResponse =
+            getRemoteDispatcher().dispatchRequest( remoteHttpCacheRequest );
+
+        log.debug( "Get [{0}] = {1}", key, remoteHttpCacheResponse );
+
+        if ( remoteHttpCacheResponse != null)
+        {
+            return remoteHttpCacheResponse.getPayload();
+        }
+
+        return null;
+    }
+
+    /**
+     * Gets multiple items from the cache matching the pattern.
+     * <p>
+     * @param cacheName
+     * @param pattern
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache matching the pattern.
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern )
+        throws IOException
+    {
+        return getMatching( cacheName, pattern, 0 );
+    }
+
+    /**
+     * Gets multiple items from the cache matching the pattern.
+     * <p>
+     * @param cacheName
+     * @param pattern
+     * @param requesterId
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache matching the pattern.
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern, long requesterId )
+        throws IOException
+    {
+        if ( !isInitialized() )
+        {
+            String message = "The Remote Http Client is not initialized. Cannot process request.";
+            log.warn( message );
+            throw new IOException( message );
+        }
+
+        RemoteCacheRequest<K, V> remoteHttpCacheRequest =
+            RemoteCacheRequestFactory.createGetMatchingRequest( cacheName, pattern, requesterId );
+
+        RemoteCacheResponse<Map<K, ICacheElement<K, V>>> remoteHttpCacheResponse =
+            getRemoteDispatcher().dispatchRequest( remoteHttpCacheRequest );
+
+        log.debug( "GetMatching [{0}] = {1}", pattern, remoteHttpCacheResponse );
+
+        return remoteHttpCacheResponse.getPayload();
+    }
+
+    /**
+     * Gets multiple items from the cache based on the given set of keys.
+     * <p>
+     * @param cacheName
+     * @param keys
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache for any of these keys
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys )
+        throws IOException
+    {
+        return getMultiple( cacheName, keys, 0 );
+    }
+
+    /**
+     * Gets multiple items from the cache based on the given set of keys.
+     * <p>
+     * @param cacheName
+     * @param keys
+     * @param requesterId
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache for any of these keys
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys, long requesterId )
+        throws IOException
+    {
+        if ( !isInitialized() )
+        {
+            String message = "The Remote Http Client is not initialized.  Cannot process request.";
+            log.warn( message );
+            throw new IOException( message );
+        }
+
+        RemoteCacheRequest<K, V> remoteHttpCacheRequest =
+            RemoteCacheRequestFactory.createGetMultipleRequest( cacheName, keys, requesterId );
+
+        RemoteCacheResponse<Map<K, ICacheElement<K, V>>> remoteHttpCacheResponse =
+            getRemoteDispatcher().dispatchRequest( remoteHttpCacheRequest );
+
+        log.debug( "GetMultiple [{0}] = {1}", keys, remoteHttpCacheResponse );
+
+        return remoteHttpCacheResponse.getPayload();
+    }
+
+    /**
+     * Removes the given key from the specified cache.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @throws IOException
+     */
+    @Override
+    public void remove( String cacheName, K key )
+        throws IOException
+    {
+        remove( cacheName, key, 0 );
+    }
+
+    /**
+     * Removes the given key from the specified cache.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @param requesterId
+     * @throws IOException
+     */
+    @Override
+    public void remove( String cacheName, K key, long requesterId )
+        throws IOException
+    {
+        if ( !isInitialized() )
+        {
+            String message = "The Remote Http Client is not initialized.  Cannot process request.";
+            log.warn( message );
+            throw new IOException( message );
+        }
+
+        RemoteCacheRequest<K, V> remoteHttpCacheRequest =
+            RemoteCacheRequestFactory.createRemoveRequest( cacheName, key, requesterId );
+
+        getRemoteDispatcher().dispatchRequest( remoteHttpCacheRequest );
+    }
+
+    /**
+     * Remove all keys from the specified cache.
+     * <p>
+     * @param cacheName
+     * @throws IOException
+     */
+    @Override
+    public void removeAll( String cacheName )
+        throws IOException
+    {
+        removeAll( cacheName, 0 );
+    }
+
+    /**
+     * Remove all keys from the sepcified cache.
+     * <p>
+     * @param cacheName
+     * @param requesterId
+     * @throws IOException
+     */
+    @Override
+    public void removeAll( String cacheName, long requesterId )
+        throws IOException
+    {
+        if ( !isInitialized() )
+        {
+            String message = "The Remote Http Client is not initialized.  Cannot process request.";
+            log.warn( message );
+            throw new IOException( message );
+        }
+
+        RemoteCacheRequest<K, V> remoteHttpCacheRequest =
+            RemoteCacheRequestFactory.createRemoveAllRequest( cacheName, requesterId );
+
+        getRemoteDispatcher().dispatchRequest( remoteHttpCacheRequest );
+    }
+
+    /**
+     * Puts a cache item to the cache.
+     * <p>
+     * @param item
+     * @throws IOException
+     */
+    @Override
+    public void update( ICacheElement<K, V> item )
+        throws IOException
+    {
+        update( item, 0 );
+    }
+
+    /**
+     * Puts a cache item to the cache.
+     * <p>
+     * @param cacheElement
+     * @param requesterId
+     * @throws IOException
+     */
+    @Override
+    public void update( ICacheElement<K, V> cacheElement, long requesterId )
+        throws IOException
+    {
+        if ( !isInitialized() )
+        {
+            String message = "The Remote Http Client is not initialized.  Cannot process request.";
+            log.warn( message );
+            throw new IOException( message );
+        }
+
+        RemoteCacheRequest<K, V> remoteHttpCacheRequest =
+            RemoteCacheRequestFactory.createUpdateRequest( cacheElement, requesterId );
+
+        getRemoteDispatcher().dispatchRequest( remoteHttpCacheRequest );
+    }
+
+    /**
+     * Frees the specified cache.
+     * <p>
+     * @param cacheName
+     * @throws IOException
+     */
+    @Override
+    public void dispose( String cacheName )
+        throws IOException
+    {
+        if ( !isInitialized() )
+        {
+            String message = "The Remote Http Client is not initialized.  Cannot process request.";
+            log.warn( message );
+            throw new IOException( message );
+        }
+
+        RemoteCacheRequest<K, V> remoteHttpCacheRequest =
+            RemoteCacheRequestFactory.createDisposeRequest( cacheName, 0 );
+
+        getRemoteDispatcher().dispatchRequest( remoteHttpCacheRequest );
+    }
+
+    /**
+     * Frees the specified cache.
+     * <p>
+     * @throws IOException
+     */
+    @Override
+    public void release()
+        throws IOException
+    {
+        // noop
+    }
+
+    /**
+     * Return the keys in this cache.
+     * <p>
+     * @param cacheName the name of the cache
+     * @see org.apache.commons.jcs3.auxiliary.AuxiliaryCache#getKeySet()
+     */
+    @Override
+    public Set<K> getKeySet( String cacheName ) throws IOException
+    {
+        if ( !isInitialized() )
+        {
+            String message = "The Remote Http Client is not initialized.  Cannot process request.";
+            log.warn( message );
+            throw new IOException( message );
+        }
+
+        RemoteCacheRequest<String, String> remoteHttpCacheRequest =
+            RemoteCacheRequestFactory.createGetKeySetRequest(cacheName, 0 );
+
+        RemoteCacheResponse<Set<K>> remoteHttpCacheResponse = getRemoteDispatcher().dispatchRequest( remoteHttpCacheRequest );
+
+        if ( remoteHttpCacheResponse != null && remoteHttpCacheResponse.getPayload() != null )
+        {
+            return remoteHttpCacheResponse.getPayload();
+        }
+
+        return Collections.emptySet();
+    }
+
+    /**
+     * Make and alive request.
+     * <p>
+     * @return true if we make a successful alive request.
+     * @throws IOException
+     */
+    @Override
+    public boolean isAlive()
+        throws IOException
+    {
+        if ( !isInitialized() )
+        {
+            String message = "The Remote Http Client is not initialized.  Cannot process request.";
+            log.warn( message );
+            throw new IOException( message );
+        }
+
+        RemoteCacheRequest<K, V> remoteHttpCacheRequest = RemoteCacheRequestFactory.createAliveCheckRequest( 0 );
+        RemoteCacheResponse<String> remoteHttpCacheResponse =
+            getRemoteDispatcher().dispatchRequest( remoteHttpCacheRequest );
+
+        if ( remoteHttpCacheResponse != null )
+        {
+            return remoteHttpCacheResponse.isSuccess();
+        }
+
+        return false;
+    }
+
+    /**
+     * @param remoteDispatcher the remoteDispatcher to set
+     */
+    public void setRemoteDispatcher( IRemoteCacheDispatcher remoteDispatcher )
+    {
+        this.remoteDispatcher = remoteDispatcher;
+    }
+
+    /**
+     * @return the remoteDispatcher
+     */
+    public IRemoteCacheDispatcher getRemoteDispatcher()
+    {
+        return remoteDispatcher;
+    }
+
+    /**
+     * @param remoteHttpCacheAttributes the remoteHttpCacheAttributes to set
+     */
+    public void setRemoteHttpCacheAttributes( RemoteHttpCacheAttributes remoteHttpCacheAttributes )
+    {
+        this.remoteHttpCacheAttributes = remoteHttpCacheAttributes;
+    }
+
+    /**
+     * @return the remoteHttpCacheAttributes
+     */
+    public RemoteHttpCacheAttributes getRemoteHttpCacheAttributes()
+    {
+        return remoteHttpCacheAttributes;
+    }
+
+    /**
+     * @param initialized the initialized to set
+     */
+    protected void setInitialized( boolean initialized )
+    {
+        this.initialized = initialized;
+    }
+
+    /**
+     * @return the initialized
+     */
+    protected boolean isInitialized()
+    {
+        return initialized;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/client/RemoteHttpCacheDispatcher.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/client/RemoteHttpCacheDispatcher.java
new file mode 100644
index 0000000..6073771
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/client/RemoteHttpCacheDispatcher.java
@@ -0,0 +1,196 @@
+package org.apache.commons.jcs3.auxiliary.remote.http.client;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheDispatcher;
+import org.apache.commons.jcs3.auxiliary.remote.value.RemoteCacheRequest;
+import org.apache.commons.jcs3.auxiliary.remote.value.RemoteCacheResponse;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.serialization.StandardSerializer;
+import org.apache.http.HttpException;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.methods.RequestBuilder;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.util.EntityUtils;
+
+/** Calls the service. */
+public class RemoteHttpCacheDispatcher
+    extends AbstractHttpClient
+    implements IRemoteCacheDispatcher
+{
+    /** Parameter encoding */
+    private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;
+
+    /** Named of the parameter */
+    private static final String PARAMETER_REQUEST_TYPE = "RequestType";
+
+    /** Named of the parameter */
+    private static final String PARAMETER_KEY = "Key";
+
+    /** Named of the parameter */
+    private static final String PARAMETER_CACHE_NAME = "CacheName";
+
+    /** The Logger. */
+    private static final Log log = LogManager.getLog( RemoteHttpCacheDispatcher.class );
+
+    /** This needs to be standard, since the other side is standard */
+    private final StandardSerializer serializer = new StandardSerializer();
+
+    /**
+     * @param remoteHttpCacheAttributes
+     */
+    public RemoteHttpCacheDispatcher( RemoteHttpCacheAttributes remoteHttpCacheAttributes )
+    {
+        super( remoteHttpCacheAttributes );
+    }
+
+    /**
+     * All requests will go through this method.
+     * <p>
+     * TODO consider taking in a URL instead of using the one in the configuration.
+     * <p>
+     * @param remoteCacheRequest
+     * @return RemoteCacheResponse
+     * @throws IOException
+     */
+    @Override
+    public <K, V, T>
+        RemoteCacheResponse<T> dispatchRequest( RemoteCacheRequest<K, V> remoteCacheRequest )
+        throws IOException
+    {
+        try
+        {
+            byte[] requestAsByteArray = serializer.serialize( remoteCacheRequest );
+
+            byte[] responseAsByteArray = processRequest( requestAsByteArray,
+                    remoteCacheRequest,
+                    getRemoteHttpCacheAttributes().getUrl());
+
+            RemoteCacheResponse<T> remoteCacheResponse = null;
+            try
+            {
+                remoteCacheResponse = serializer.deSerialize( responseAsByteArray, null );
+            }
+            catch ( ClassNotFoundException e )
+            {
+                log.error( "Couldn't deserialize the response.", e );
+            }
+            return remoteCacheResponse;
+        }
+        catch ( Exception e )
+        {
+            throw new IOException("Problem dispatching request.", e);
+        }
+    }
+
+    /**
+     * Process single request
+     *
+     * @param requestAsByteArray request body
+     * @param remoteCacheRequest the cache request
+     * @param url target url
+     *
+     * @return byte[] - the response
+     *
+     * @throws IOException
+     * @throws HttpException
+     */
+    protected <K, V> byte[] processRequest( byte[] requestAsByteArray,
+            RemoteCacheRequest<K, V> remoteCacheRequest, String url )
+        throws IOException, HttpException
+    {
+        RequestBuilder builder = RequestBuilder.post( url ).setCharset( DEFAULT_ENCODING );
+
+        if ( getRemoteHttpCacheAttributes().isIncludeCacheNameAsParameter()
+            && remoteCacheRequest.getCacheName() != null )
+        {
+            builder.addParameter( PARAMETER_CACHE_NAME, remoteCacheRequest.getCacheName() );
+        }
+        if ( getRemoteHttpCacheAttributes().isIncludeKeysAndPatternsAsParameter() )
+        {
+            String keyValue = "";
+            switch ( remoteCacheRequest.getRequestType() )
+            {
+                case GET:
+                case REMOVE:
+                case GET_KEYSET:
+                    keyValue = remoteCacheRequest.getKey().toString();
+                    break;
+                case GET_MATCHING:
+                    keyValue = remoteCacheRequest.getPattern();
+                    break;
+                case GET_MULTIPLE:
+                    keyValue = remoteCacheRequest.getKeySet().toString();
+                    break;
+                case UPDATE:
+                    keyValue = remoteCacheRequest.getCacheElement().getKey().toString();
+                    break;
+                default:
+                    break;
+            }
+            builder.addParameter( PARAMETER_KEY, keyValue );
+        }
+        if ( getRemoteHttpCacheAttributes().isIncludeRequestTypeasAsParameter() )
+        {
+            builder.addParameter( PARAMETER_REQUEST_TYPE,
+                remoteCacheRequest.getRequestType().toString() );
+        }
+
+        builder.setEntity(new ByteArrayEntity( requestAsByteArray ));
+        HttpResponse httpResponse = doWebserviceCall( builder );
+        byte[] response = EntityUtils.toByteArray( httpResponse.getEntity() );
+        return response;
+    }
+
+    /**
+     * Called before the execute call on the client.
+     * <p>
+     * @param requestBuilder http method request builder
+     *
+     * @throws IOException
+     */
+    @Override
+    protected void preProcessWebserviceCall( RequestBuilder requestBuilder )
+        throws IOException
+    {
+        // do nothing. Child can override.
+    }
+
+    /**
+     * Called after the execute call on the client.
+     * <p>
+     * @param request http request
+     * @param httpState result of execution
+     *
+     * @throws IOException
+     */
+    @Override
+    protected void postProcessWebserviceCall( HttpUriRequest request, HttpResponse httpState )
+        throws IOException
+    {
+        // do nothing. Child can override.
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/client/RemoteHttpCacheFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/client/RemoteHttpCacheFactory.java
new file mode 100644
index 0000000..91e4281
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/client/RemoteHttpCacheFactory.java
@@ -0,0 +1,146 @@
+package org.apache.commons.jcs3.auxiliary.remote.http.client;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCacheFactory;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCache;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.remote.RemoteCacheNoWait;
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheClient;
+import org.apache.commons.jcs3.auxiliary.remote.http.client.behavior.IRemoteHttpCacheClient;
+import org.apache.commons.jcs3.auxiliary.remote.server.behavior.RemoteType;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.config.OptionConverter;
+
+/**
+ * The RemoteCacheFactory creates remote caches for the cache hub. It returns a no wait facade which
+ * is a wrapper around a no wait. The no wait object is either an active connection to a remote
+ * cache or a balking zombie if the remote cache is not accessible. It should be transparent to the
+ * clients.
+ */
+public class RemoteHttpCacheFactory
+    extends AbstractAuxiliaryCacheFactory
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog( RemoteHttpCacheFactory.class );
+
+    /** Monitor thread instance */
+    private RemoteHttpCacheMonitor monitor;
+
+    /**
+     * For LOCAL clients we get a handle to all the failovers, but we do not register a listener
+     * with them. We create the RemoteCacheManager, but we do not get a cache.
+     * <p>
+     * The failover runner will get a cache from the manager. When the primary is restored it will
+     * tell the manager for the failover to deregister the listener.
+     * <p>
+     * @param iaca
+     * @param cacheMgr
+     * @param cacheEventLogger
+     * @param elementSerializer
+     * @return AuxiliaryCache
+     */
+    @Override
+    public <K, V> AuxiliaryCache<K, V> createCache( AuxiliaryCacheAttributes iaca, ICompositeCacheManager cacheMgr,
+                                       ICacheEventLogger cacheEventLogger, IElementSerializer elementSerializer )
+    {
+        RemoteHttpCacheAttributes rca = (RemoteHttpCacheAttributes) iaca;
+
+        // TODO, use the configured value.
+        rca.setRemoteType( RemoteType.LOCAL );
+
+        RemoteHttpClientListener<K, V> listener = new RemoteHttpClientListener<>( rca, cacheMgr, elementSerializer );
+
+        IRemoteHttpCacheClient<K, V> remoteService = createRemoteHttpCacheClientForAttributes(rca);
+
+        IRemoteCacheClient<K, V> remoteCacheClient =
+                new RemoteHttpCache<>( rca, remoteService, listener, monitor );
+        remoteCacheClient.setCacheEventLogger( cacheEventLogger );
+        remoteCacheClient.setElementSerializer( elementSerializer );
+
+        RemoteCacheNoWait<K, V> remoteCacheNoWait = new RemoteCacheNoWait<>( remoteCacheClient );
+        remoteCacheNoWait.setCacheEventLogger( cacheEventLogger );
+        remoteCacheNoWait.setElementSerializer( elementSerializer );
+
+        return remoteCacheNoWait;
+    }
+
+    /**
+     * This is an extension point. The manager and other classes will only create
+     * RemoteHttpCacheClient through this method.
+
+     * @param cattr the cache configuration
+     * @return the client instance
+     */
+    protected <V, K> IRemoteHttpCacheClient<K, V> createRemoteHttpCacheClientForAttributes(RemoteHttpCacheAttributes cattr)
+    {
+        IRemoteHttpCacheClient<K, V> remoteService = OptionConverter.instantiateByClassName( cattr
+                        .getRemoteHttpClientClassName(), null );
+
+        if ( remoteService == null )
+        {
+            log.info( "Creating the default client for {0}",
+                    () -> cattr.getCacheName());
+            remoteService = new RemoteHttpCacheClient<>();
+        }
+
+        remoteService.initialize( cattr );
+        return remoteService;
+    }
+
+    /**
+     * @see org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCacheFactory#initialize()
+     */
+    @Override
+    public void initialize()
+    {
+        super.initialize();
+        monitor = new RemoteHttpCacheMonitor(this);
+        monitor.setDaemon(true);
+        monitor.start();
+    }
+
+    /**
+     * @see org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCacheFactory#dispose()
+     */
+    @Override
+    public void dispose()
+    {
+        if (monitor != null)
+        {
+            monitor.notifyShutdown();
+            try
+            {
+                monitor.join(5000);
+            }
+            catch (InterruptedException e)
+            {
+                // swallow
+            }
+            monitor = null;
+        }
+
+        super.dispose();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/client/RemoteHttpCacheMonitor.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/client/RemoteHttpCacheMonitor.java
new file mode 100644
index 0000000..34e3ae4
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/client/RemoteHttpCacheMonitor.java
@@ -0,0 +1,136 @@
+package org.apache.commons.jcs3.auxiliary.remote.http.client;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCacheMonitor;
+import org.apache.commons.jcs3.auxiliary.remote.http.client.behavior.IRemoteHttpCacheClient;
+import org.apache.commons.jcs3.engine.CacheStatus;
+
+/**
+ * Upon the notification of a connection error, the monitor changes to operate in a time driven
+ * mode. That is, it attempts to recover the connections on a periodic basis. When all failed
+ * connections are restored, it changes back to the failure driven mode.
+ */
+public class RemoteHttpCacheMonitor extends AbstractAuxiliaryCacheMonitor
+{
+    /** Set of remote caches to monitor. This are added on error, if not before. */
+    private final ConcurrentHashMap<RemoteHttpCache<?, ?>, RemoteHttpCache<?, ?>> remoteHttpCaches;
+
+    /** Factory instance */
+    private RemoteHttpCacheFactory factory = null;
+
+    /**
+     * Constructor for the RemoteCacheMonitor object
+     *
+     * @param factory the factory to set
+     */
+    public RemoteHttpCacheMonitor(RemoteHttpCacheFactory factory)
+    {
+        super("JCS-RemoteHttpCacheMonitor");
+        this.factory = factory;
+        this.remoteHttpCaches = new ConcurrentHashMap<>();
+        setIdlePeriod(3000L);
+    }
+
+    /**
+     * Notifies the cache monitor that an error occurred, and kicks off the error recovery process.
+     * <p>
+     * @param remoteCache
+     */
+    public void notifyError( RemoteHttpCache<?, ?> remoteCache )
+    {
+        if ( log.isInfoEnabled() )
+        {
+            log.info( "Notified of an error. " + remoteCache );
+        }
+
+        remoteHttpCaches.put( remoteCache, remoteCache );
+        notifyError();
+    }
+
+    /**
+     * Clean up all resources before shutdown
+     */
+    @Override
+    protected void dispose()
+    {
+        this.remoteHttpCaches.clear();
+    }
+
+    // Avoid the use of any synchronization in the process of monitoring for
+    // performance reasons.
+    // If exception is thrown owing to synchronization,
+    // just skip the monitoring until the next round.
+    /** Main processing method for the RemoteHttpCacheMonitor object */
+    @Override
+    protected void doWork()
+    {
+        // If no factory has been set, skip
+        if (factory == null)
+        {
+            return;
+        }
+
+        // If any cache is in error, it strongly suggests all caches
+        // managed by the same RmicCacheManager instance are in error. So we fix
+        // them once and for all.
+        for (RemoteHttpCache<?, ?> remoteCache : this.remoteHttpCaches.values())
+        {
+            try
+            {
+                if ( remoteCache.getStatus() == CacheStatus.ERROR )
+                {
+                    RemoteHttpCacheAttributes attributes = remoteCache.getRemoteHttpCacheAttributes();
+
+                    IRemoteHttpCacheClient<Serializable, Serializable> remoteService =
+                            factory.createRemoteHttpCacheClientForAttributes( attributes );
+
+                    if ( log.isInfoEnabled() )
+                    {
+                        log.info( "Performing Alive check on service " + remoteService );
+                    }
+                    // If we can't fix them, just skip and re-try in
+                    // the next round.
+                    if ( remoteService.isAlive() )
+                    {
+                        remoteCache.fixCache( remoteService );
+                    }
+                    else
+                    {
+                        allright.set(false);
+                    }
+                    break;
+                }
+            }
+            catch ( IOException ex )
+            {
+                allright.set(false);
+                // Problem encountered in fixing the caches managed by a
+                // RemoteCacheManager instance.
+                // Soldier on to the next RemoteHttpCache.
+                log.error( ex );
+            }
+        }
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/client/RemoteHttpClientListener.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/client/RemoteHttpClientListener.java
new file mode 100644
index 0000000..0247204
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/client/RemoteHttpClientListener.java
@@ -0,0 +1,52 @@
+package org.apache.commons.jcs3.auxiliary.remote.http.client;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.auxiliary.remote.AbstractRemoteCacheListener;
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheAttributes;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+
+/** Does nothing */
+public class RemoteHttpClientListener<K, V>
+    extends AbstractRemoteCacheListener<K, V>
+{
+    /**
+     * Only need one since it does work for all regions, just reference by multiple region names.
+     * <p>
+     * The constructor exports this object, making it available to receive incoming calls. The
+     * callback port is anonymous unless a local port value was specified in the configuration.
+     * <p>
+     * @param irca cache configuration
+     * @param cacheMgr the cache hub
+     * @param elementSerializer a custom serializer
+     */
+    public RemoteHttpClientListener( IRemoteCacheAttributes irca, ICompositeCacheManager cacheMgr, IElementSerializer elementSerializer )
+    {
+        super( irca, cacheMgr, elementSerializer );
+    }
+
+    /** Nothing */
+    @Override
+    public void dispose()
+    {
+        // noop
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/client/behavior/IRemoteHttpCacheClient.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/client/behavior/IRemoteHttpCacheClient.java
new file mode 100644
index 0000000..1d6933a
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/client/behavior/IRemoteHttpCacheClient.java
@@ -0,0 +1,51 @@
+package org.apache.commons.jcs3.auxiliary.remote.http.client.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+
+import org.apache.commons.jcs3.auxiliary.remote.http.client.RemoteHttpCacheAttributes;
+import org.apache.commons.jcs3.engine.behavior.ICacheServiceNonLocal;
+
+
+/**
+ * It's not entirely clear that this interface is needed. I simply wanted the initialization method.
+ * This could be added to the ICacheSerice method.
+ */
+public interface IRemoteHttpCacheClient<K, V>
+    extends ICacheServiceNonLocal<K, V>
+{
+    /**
+     * The provides an extension point. If you want to extend this and use a special dispatcher,
+     * here is the place to do it.
+     * <p>
+     * @param attributes
+     */
+    void initialize( RemoteHttpCacheAttributes attributes );
+
+    /**
+     * Make and alive request.
+     * <p>
+     * @return true if we make a successful alive request.
+     * @throws IOException
+     */
+    boolean isAlive()
+        throws IOException;
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/server/AbstractRemoteCacheService.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/server/AbstractRemoteCacheService.java
new file mode 100644
index 0000000..e84e575
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/server/AbstractRemoteCacheService.java
@@ -0,0 +1,601 @@
+package org.apache.commons.jcs3.auxiliary.remote.http.server;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheServiceNonLocal;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+import org.apache.commons.jcs3.engine.logging.CacheEvent;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEvent;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * This class contains common methods for remote cache services. Eventually I hope to extract out
+ * much of the RMI server to use this as well. I'm starting with the Http service.
+ */
+public abstract class AbstractRemoteCacheService<K, V>
+    implements ICacheServiceNonLocal<K, V>
+{
+    /** An optional event logger */
+    private transient ICacheEventLogger cacheEventLogger;
+
+    /** The central hub */
+    private ICompositeCacheManager cacheManager;
+
+    /** Name of the event log source. */
+    private String eventLogSourceName = "AbstractRemoteCacheService";
+
+    /** Number of puts into the cache. */
+    private int puts = 0;
+
+    /** The interval at which we will log updates. */
+    private final int logInterval = 100;
+
+    /** log instance */
+    private static final Log log = LogManager.getLog( AbstractRemoteCacheService.class );
+
+    /**
+     * Creates the super with the needed items.
+     * <p>
+     * @param cacheManager
+     * @param cacheEventLogger
+     */
+    public AbstractRemoteCacheService( ICompositeCacheManager cacheManager, ICacheEventLogger cacheEventLogger )
+    {
+        this.cacheManager = cacheManager;
+        this.cacheEventLogger = cacheEventLogger;
+    }
+
+    /**
+     * @param item
+     * @throws IOException
+     */
+    @Override
+    public void update( ICacheElement<K, V> item )
+        throws IOException
+    {
+        update( item, 0 );
+    }
+
+    /**
+     * The internal processing is wrapped in event logging calls.
+     * <p>
+     * @param item
+     * @param requesterId
+     * @throws IOException
+     */
+    @Override
+    public void update( ICacheElement<K, V> item, long requesterId )
+        throws IOException
+    {
+        ICacheEvent<ICacheElement<K, V>> cacheEvent = createICacheEvent( item, requesterId, ICacheEventLogger.UPDATE_EVENT );
+        try
+        {
+            logUpdateInfo( item );
+
+            processUpdate( item, requesterId );
+        }
+        finally
+        {
+            logICacheEvent( cacheEvent );
+        }
+    }
+
+    /**
+     * The internal processing is wrapped in event logging calls.
+     * <p>
+     * @param item
+     * @param requesterId
+     * @throws IOException
+     */
+    abstract void processUpdate( ICacheElement<K, V> item, long requesterId )
+        throws IOException;
+
+    /**
+     * Log some details.
+     * <p>
+     * @param item
+     */
+    private void logUpdateInfo( ICacheElement<K, V> item )
+    {
+        if ( log.isInfoEnabled() )
+        {
+            // not thread safe, but it doesn't have to be accurate
+            puts++;
+            if ( puts % logInterval == 0 )
+            {
+                log.info( "puts = {0}", puts );
+            }
+        }
+
+        log.debug( "In update, put [{0}] in [{1}]", () -> item.getKey(),
+                () -> item.getCacheName() );
+    }
+
+    /**
+     * Returns a cache value from the specified remote cache; or null if the cache or key does not
+     * exist.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @return ICacheElement
+     * @throws IOException
+     */
+    @Override
+    public ICacheElement<K, V> get( String cacheName, K key )
+        throws IOException
+    {
+        return this.get( cacheName, key, 0 );
+    }
+
+    /**
+     * Returns a cache bean from the specified cache; or null if the key does not exist.
+     * <p>
+     * Adding the requestor id, allows the cache to determine the source of the get.
+     * <p>
+     * The internal processing is wrapped in event logging calls.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @param requesterId
+     * @return ICacheElement
+     * @throws IOException
+     */
+    @Override
+    public ICacheElement<K, V> get( String cacheName, K key, long requesterId )
+        throws IOException
+    {
+        ICacheElement<K, V> element = null;
+        ICacheEvent<K> cacheEvent = createICacheEvent( cacheName, key, requesterId, ICacheEventLogger.GET_EVENT );
+        try
+        {
+            element = processGet( cacheName, key, requesterId );
+        }
+        finally
+        {
+            logICacheEvent( cacheEvent );
+        }
+        return element;
+    }
+
+    /**
+     * Returns a cache bean from the specified cache; or null if the key does not exist.
+     * <p>
+     * Adding the requestor id, allows the cache to determine the source of the get.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @param requesterId
+     * @return ICacheElement
+     * @throws IOException
+     */
+    abstract ICacheElement<K, V> processGet( String cacheName, K key, long requesterId )
+        throws IOException;
+
+    /**
+     * Gets all matching items.
+     * <p>
+     * @param cacheName
+     * @param pattern
+     * @return Map of keys and wrapped objects
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern )
+        throws IOException
+    {
+        return getMatching( cacheName, pattern, 0 );
+    }
+
+    /**
+     * Retrieves all matching keys.
+     * <p>
+     * @param cacheName
+     * @param pattern
+     * @param requesterId
+     * @return Map of keys and wrapped objects
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern, long requesterId )
+        throws IOException
+    {
+        ICacheEvent<String> cacheEvent = createICacheEvent( cacheName, pattern, requesterId,
+                                                    ICacheEventLogger.GETMATCHING_EVENT );
+        try
+        {
+            return processGetMatching( cacheName, pattern, requesterId );
+        }
+        finally
+        {
+            logICacheEvent( cacheEvent );
+        }
+    }
+
+    /**
+     * Retrieves all matching keys.
+     * <p>
+     * @param cacheName
+     * @param pattern
+     * @param requesterId
+     * @return Map of keys and wrapped objects
+     * @throws IOException
+     */
+    abstract Map<K, ICacheElement<K, V>> processGetMatching( String cacheName, String pattern, long requesterId )
+        throws IOException;
+
+    /**
+     * Gets multiple items from the cache based on the given set of keys.
+     * <p>
+     * @param cacheName
+     * @param keys
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache for any of these keys
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys )
+        throws IOException
+    {
+        return this.getMultiple( cacheName, keys, 0 );
+    }
+
+    /**
+     * Gets multiple items from the cache based on the given set of keys.
+     * <p>
+     * The internal processing is wrapped in event logging calls.
+     * <p>
+     * @param cacheName
+     * @param keys
+     * @param requesterId
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache for any of these keys
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys, long requesterId )
+        throws IOException
+    {
+        ICacheEvent<Serializable> cacheEvent = createICacheEvent( cacheName, (Serializable) keys, requesterId,
+                                                    ICacheEventLogger.GETMULTIPLE_EVENT );
+        try
+        {
+            return processGetMultiple( cacheName, keys, requesterId );
+        }
+        finally
+        {
+            logICacheEvent( cacheEvent );
+        }
+    }
+
+    /**
+     * Gets multiple items from the cache based on the given set of keys.
+     * <p>
+     * @param cacheName
+     * @param keys
+     * @param requesterId
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache for any of these keys
+     * @throws IOException
+     */
+    abstract Map<K, ICacheElement<K, V>> processGetMultiple( String cacheName, Set<K> keys, long requesterId )
+        throws IOException;
+
+    /**
+     * Return the keys in this cache.
+     * <p>
+     * @see org.apache.commons.jcs3.auxiliary.AuxiliaryCache#getKeySet()
+     */
+    @Override
+    public Set<K> getKeySet( String cacheName )
+    {
+        return processGetKeySet( cacheName );
+    }
+
+    /**
+     * Gets the set of keys of objects currently in the cache.
+     * <p>
+     * @param cacheName
+     * @return Set
+     */
+    public Set<K> processGetKeySet( String cacheName )
+    {
+        CompositeCache<K, V> cache = getCacheManager().getCache( cacheName );
+
+        return cache.getKeySet();
+    }
+
+    /**
+     * Removes the given key from the specified remote cache. Defaults the listener id to 0.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @throws IOException
+     */
+    @Override
+    public void remove( String cacheName, K key )
+        throws IOException
+    {
+        remove( cacheName, key, 0 );
+    }
+
+    /**
+     * Remove the key from the cache region and don't tell the source listener about it.
+     * <p>
+     * The internal processing is wrapped in event logging calls.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @param requesterId
+     * @throws IOException
+     */
+    @Override
+    public void remove( String cacheName, K key, long requesterId )
+        throws IOException
+    {
+        ICacheEvent<K> cacheEvent = createICacheEvent( cacheName, key, requesterId, ICacheEventLogger.REMOVE_EVENT );
+        try
+        {
+            processRemove( cacheName, key, requesterId );
+        }
+        finally
+        {
+            logICacheEvent( cacheEvent );
+        }
+    }
+
+    /**
+     * Remove the key from the cache region and don't tell the source listener about it.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @param requesterId
+     * @throws IOException
+     */
+    abstract void processRemove( String cacheName, K key, long requesterId )
+        throws IOException;
+
+    /**
+     * Remove all keys from the specified remote cache.
+     * <p>
+     * @param cacheName
+     * @throws IOException
+     */
+    @Override
+    public void removeAll( String cacheName )
+        throws IOException
+    {
+        removeAll( cacheName, 0 );
+    }
+
+    /**
+     * Remove all keys from the specified remote cache.
+     * <p>
+     * The internal processing is wrapped in event logging calls.
+     * <p>
+     * @param cacheName
+     * @param requesterId
+     * @throws IOException
+     */
+    @Override
+    public void removeAll( String cacheName, long requesterId )
+        throws IOException
+    {
+        ICacheEvent<String> cacheEvent = createICacheEvent( cacheName, "all", requesterId, ICacheEventLogger.REMOVEALL_EVENT );
+        try
+        {
+            processRemoveAll( cacheName, requesterId );
+        }
+        finally
+        {
+            logICacheEvent( cacheEvent );
+        }
+    }
+
+    /**
+     * Remove all keys from the specified remote cache.
+     * <p>
+     * @param cacheName
+     * @param requesterId
+     * @throws IOException
+     */
+    abstract void processRemoveAll( String cacheName, long requesterId )
+        throws IOException;
+
+    /**
+     * Frees the specified remote cache.
+     * <p>
+     * @param cacheName
+     * @throws IOException
+     */
+    @Override
+    public void dispose( String cacheName )
+        throws IOException
+    {
+        dispose( cacheName, 0 );
+    }
+
+    /**
+     * Frees the specified remote cache.
+     * <p>
+     * @param cacheName
+     * @param requesterId
+     * @throws IOException
+     */
+    public void dispose( String cacheName, long requesterId )
+        throws IOException
+    {
+        ICacheEvent<String> cacheEvent = createICacheEvent( cacheName, "none", requesterId, ICacheEventLogger.DISPOSE_EVENT );
+        try
+        {
+            processDispose( cacheName, requesterId );
+        }
+        finally
+        {
+            logICacheEvent( cacheEvent );
+        }
+    }
+
+    /**
+     * @param cacheName
+     * @param requesterId
+     * @throws IOException
+     */
+    abstract void processDispose( String cacheName, long requesterId )
+        throws IOException;
+
+    /**
+     * Gets the stats attribute of the RemoteCacheServer object.
+     * <p>
+     * @return The stats value
+     * @throws IOException
+     */
+    public String getStats()
+        throws IOException
+    {
+        return cacheManager.getStats();
+    }
+
+    /**
+     * Logs an event if an event logger is configured.
+     * <p>
+     * @param item
+     * @param requesterId
+     * @param eventName
+     * @return ICacheEvent
+     */
+    protected ICacheEvent<ICacheElement<K, V>> createICacheEvent( ICacheElement<K, V> item, long requesterId, String eventName )
+    {
+        if ( cacheEventLogger == null )
+        {
+            return new CacheEvent<>();
+        }
+        String ipAddress = getExtraInfoForRequesterId( requesterId );
+        return cacheEventLogger.createICacheEvent( getEventLogSourceName(), item.getCacheName(), eventName, ipAddress,
+                                                   item );
+    }
+
+    /**
+     * Logs an event if an event logger is configured.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @param requesterId
+     * @param eventName
+     * @return ICacheEvent
+     */
+    protected <T> ICacheEvent<T> createICacheEvent( String cacheName, T key, long requesterId, String eventName )
+    {
+        if ( cacheEventLogger == null )
+        {
+            return new CacheEvent<>();
+        }
+        String ipAddress = getExtraInfoForRequesterId( requesterId );
+        return cacheEventLogger.createICacheEvent( getEventLogSourceName(), cacheName, eventName, ipAddress, key );
+    }
+
+    /**
+     * Logs an event if an event logger is configured.
+     * <p>
+     * @param source
+     * @param eventName
+     * @param optionalDetails
+     */
+    protected void logApplicationEvent( String source, String eventName, String optionalDetails )
+    {
+        if ( cacheEventLogger != null )
+        {
+            cacheEventLogger.logApplicationEvent( source, eventName, optionalDetails );
+        }
+    }
+
+    /**
+     * Logs an event if an event logger is configured.
+     * <p>
+     * @param cacheEvent
+     */
+    protected <T> void logICacheEvent( ICacheEvent<T> cacheEvent )
+    {
+        if ( cacheEventLogger != null )
+        {
+            cacheEventLogger.logICacheEvent( cacheEvent );
+        }
+    }
+
+    /**
+     * Ip address for the client, if one is stored.
+     * <p>
+     * Protected for testing.
+     * <p>
+     * @param requesterId
+     * @return String
+     */
+    protected abstract String getExtraInfoForRequesterId( long requesterId );
+
+    /**
+     * Allows it to be injected.
+     * <p>
+     * @param cacheEventLogger
+     */
+    public void setCacheEventLogger( ICacheEventLogger cacheEventLogger )
+    {
+        this.cacheEventLogger = cacheEventLogger;
+    }
+
+    /**
+     * @param cacheManager the cacheManager to set
+     */
+    protected void setCacheManager( ICompositeCacheManager cacheManager )
+    {
+        this.cacheManager = cacheManager;
+    }
+
+    /**
+     * @return the cacheManager
+     */
+    protected ICompositeCacheManager getCacheManager()
+    {
+        return cacheManager;
+    }
+
+    /**
+     * @param eventLogSourceName the eventLogSourceName to set
+     */
+    protected void setEventLogSourceName( String eventLogSourceName )
+    {
+        this.eventLogSourceName = eventLogSourceName;
+    }
+
+    /**
+     * @return the eventLogSourceName
+     */
+    protected String getEventLogSourceName()
+    {
+        return eventLogSourceName;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/server/RemoteHttpCacheServerAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/server/RemoteHttpCacheServerAttributes.java
new file mode 100644
index 0000000..aa14823
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/server/RemoteHttpCacheServerAttributes.java
@@ -0,0 +1,95 @@
+package org.apache.commons.jcs3.auxiliary.remote.http.server;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCacheAttributes;
+
+/**
+ * Configuration for the RemoteHttpCacheServer. Most of these properties are used only by the
+ * service.
+ */
+public class RemoteHttpCacheServerAttributes
+    extends AbstractAuxiliaryCacheAttributes
+{
+    /** Don't change. */
+    private static final long serialVersionUID = -3987239306108780496L;
+
+    /** Can a cluster remote put to other remotes */
+    private boolean localClusterConsistency = true;
+
+    /** Can a cluster remote get from other remotes */
+    private boolean allowClusterGet = true;
+
+    /**
+     * Should cluster updates be propagated to the locals
+     * <p>
+     * @return The localClusterConsistency value
+     */
+    public boolean isLocalClusterConsistency()
+    {
+        return localClusterConsistency;
+    }
+
+    /**
+     * Should cluster updates be propagated to the locals
+     * <p>
+     * @param r The new localClusterConsistency value
+     */
+    public void setLocalClusterConsistency( boolean r )
+    {
+        this.localClusterConsistency = r;
+    }
+
+    /**
+     * Should gets from non-cluster clients be allowed to get from other remote auxiliaries.
+     * <p>
+     * @return The localClusterConsistency value
+     */
+    public boolean isAllowClusterGet()
+    {
+        return allowClusterGet;
+    }
+
+    /**
+     * Should we try to get from other cluster servers if we don't find the items locally.
+     * <p>
+     * @param r The new localClusterConsistency value
+     */
+    public void setAllowClusterGet( boolean r )
+    {
+        allowClusterGet = r;
+    }
+
+    /**
+     * @return String details
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        buf.append( "\nRemoteHttpCacheServiceAttributes" );
+        buf.append( "\n cacheName = [" + this.getCacheName() + "]" );
+        buf.append( "\n allowClusterGet = [" + this.isAllowClusterGet() + "]" );
+        buf.append( "\n localClusterConsistency = [" + this.isLocalClusterConsistency() + "]" );
+        buf.append( "\n eventQueueType = [" + this.getEventQueueType() + "]" );
+        buf.append( "\n eventQueuePoolName = [" + this.getEventQueuePoolName() + "]" );
+        return buf.toString();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/server/RemoteHttpCacheService.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/server/RemoteHttpCacheService.java
new file mode 100644
index 0000000..4f0c251
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/server/RemoteHttpCacheService.java
@@ -0,0 +1,269 @@
+package org.apache.commons.jcs3.auxiliary.remote.http.server;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+
+/**
+ * This does the work. It's called by the processor. The base class wraps the processing calls in
+ * event logs, if an event logger is present.
+ * <p>
+ * For now we assume that all clients are non-cluster clients. And listener notification is not
+ * supported.
+ */
+public class RemoteHttpCacheService<K, V>
+    extends AbstractRemoteCacheService<K, V>
+{
+    /** The name used in the event logs. */
+    private static final String EVENT_LOG_SOURCE_NAME = "RemoteHttpCacheServer";
+
+    /** The configuration */
+    private final RemoteHttpCacheServerAttributes remoteHttpCacheServerAttributes;
+
+    /**
+     * Create a process with a cache manager.
+     * <p>
+     * @param cacheManager
+     * @param remoteHttpCacheServerAttributes
+     * @param cacheEventLogger
+     */
+    public RemoteHttpCacheService( ICompositeCacheManager cacheManager,
+                                   RemoteHttpCacheServerAttributes remoteHttpCacheServerAttributes,
+                                   ICacheEventLogger cacheEventLogger )
+    {
+        super( cacheManager, cacheEventLogger );
+        setEventLogSourceName( EVENT_LOG_SOURCE_NAME );
+        this.remoteHttpCacheServerAttributes = remoteHttpCacheServerAttributes;
+    }
+
+    /**
+     * Processes a get request.
+     * <p>
+     * If isAllowClusterGet is enabled we will treat this as a normal request or non-remote origins.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @param requesterId
+     * @return ICacheElement
+     * @throws IOException
+     */
+    @Override
+    public ICacheElement<K, V> processGet( String cacheName, K key, long requesterId )
+        throws IOException
+    {
+        CompositeCache<K, V> cache = getCacheManager().getCache( cacheName );
+
+        boolean keepLocal = !remoteHttpCacheServerAttributes.isAllowClusterGet();
+        if ( keepLocal )
+        {
+            return cache.localGet( key );
+        }
+        else
+        {
+            return cache.get( key );
+        }
+    }
+
+    /**
+     * Processes a get request.
+     * <p>
+     * If isAllowClusterGet is enabled we will treat this as a normal request of non-remote
+     * origination.
+     * <p>
+     * @param cacheName
+     * @param keys
+     * @param requesterId
+     * @return Map
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> processGetMultiple( String cacheName, Set<K> keys, long requesterId )
+        throws IOException
+    {
+        CompositeCache<K, V> cache = getCacheManager().getCache( cacheName );
+
+        boolean keepLocal = !remoteHttpCacheServerAttributes.isAllowClusterGet();
+        if ( keepLocal )
+        {
+            return cache.localGetMultiple( keys );
+        }
+        else
+        {
+            return cache.getMultiple( keys );
+        }
+    }
+
+    /**
+     * Processes a get request.
+     * <p>
+     * If isAllowClusterGet is enabled we will treat this as a normal request of non-remote
+     * origination.
+     * <p>
+     * @param cacheName
+     * @param pattern
+     * @param requesterId
+     * @return Map
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> processGetMatching( String cacheName, String pattern, long requesterId )
+        throws IOException
+    {
+        CompositeCache<K, V> cache = getCacheManager().getCache( cacheName );
+
+        boolean keepLocal = !remoteHttpCacheServerAttributes.isAllowClusterGet();
+        if ( keepLocal )
+        {
+            return cache.localGetMatching( pattern );
+        }
+        else
+        {
+            return cache.getMatching( pattern );
+        }
+    }
+
+    /**
+     * Processes an update request.
+     * <p>
+     * If isLocalClusterConsistency is enabled we will treat this as a normal request of non-remote
+     * origination.
+     * <p>
+     * @param item
+     * @param requesterId
+     * @throws IOException
+     */
+    @Override
+    public void processUpdate( ICacheElement<K, V> item, long requesterId )
+        throws IOException
+    {
+        CompositeCache<K, V> cache = getCacheManager().getCache( item.getCacheName() );
+
+        boolean keepLocal = !remoteHttpCacheServerAttributes.isLocalClusterConsistency();
+        if ( keepLocal )
+        {
+            cache.localUpdate( item );
+        }
+        else
+        {
+            cache.update( item );
+        }
+    }
+
+    /**
+     * Processes a remove request.
+     * <p>
+     * If isLocalClusterConsistency is enabled we will treat this as a normal request of non-remote
+     * origination.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @param requesterId
+     * @throws IOException
+     */
+    @Override
+    public void processRemove( String cacheName, K key, long requesterId )
+        throws IOException
+    {
+        CompositeCache<K, V> cache = getCacheManager().getCache( cacheName );
+
+        boolean keepLocal = !remoteHttpCacheServerAttributes.isLocalClusterConsistency();
+        if ( keepLocal )
+        {
+            cache.localRemove( key );
+        }
+        else
+        {
+            cache.remove( key );
+        }
+    }
+
+    /**
+     * Processes a removeAll request.
+     * <p>
+     * If isLocalClusterConsistency is enabled we will treat this as a normal request of non-remote
+     * origination.
+     * <p>
+     * @param cacheName
+     * @param requesterId
+     * @throws IOException
+     */
+    @Override
+    public void processRemoveAll( String cacheName, long requesterId )
+        throws IOException
+    {
+        CompositeCache<K, V> cache = getCacheManager().getCache( cacheName );
+
+        boolean keepLocal = !remoteHttpCacheServerAttributes.isLocalClusterConsistency();
+        if ( keepLocal )
+        {
+            cache.localRemoveAll();
+        }
+        else
+        {
+            cache.removeAll();
+        }
+    }
+
+    /**
+     * Processes a shutdown request.
+     * <p>
+     * @param cacheName
+     * @param requesterId
+     * @throws IOException
+     */
+    @Override
+    public void processDispose( String cacheName, long requesterId )
+        throws IOException
+    {
+        CompositeCache<K, V> cache = getCacheManager().getCache( cacheName );
+        cache.dispose();
+    }
+
+    /**
+     * This general method should be deprecated.
+     * <p>
+     * @throws IOException
+     */
+    @Override
+    public void release()
+        throws IOException
+    {
+        //nothing.
+    }
+
+    /**
+     * This is called by the event log.
+     * <p>
+     * @param requesterId
+     * @return requesterId + ""
+     */
+    @Override
+    protected String getExtraInfoForRequesterId( long requesterId )
+    {
+        return requesterId + "";
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/server/RemoteHttpCacheServlet.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/server/RemoteHttpCacheServlet.java
new file mode 100644
index 0000000..280278b
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/http/server/RemoteHttpCacheServlet.java
@@ -0,0 +1,380 @@
+package org.apache.commons.jcs3.auxiliary.remote.http.server;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.jcs3.access.exception.CacheException;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheConfigurator;
+import org.apache.commons.jcs3.auxiliary.remote.http.behavior.IRemoteHttpCacheConstants;
+import org.apache.commons.jcs3.auxiliary.remote.value.RemoteCacheRequest;
+import org.apache.commons.jcs3.auxiliary.remote.value.RemoteCacheResponse;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheServiceNonLocal;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
+import org.apache.commons.jcs3.engine.control.CompositeCacheManager;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+import org.apache.commons.jcs3.io.ObjectInputStreamClassLoaderAware;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.config.PropertySetter;
+import org.apache.commons.jcs3.utils.serialization.StandardSerializer;
+
+/**
+ * This servlet simply reads and writes objects. The requests are packaged in a general wrapper. The
+ * processor works on the wrapper object and returns a response wrapper.
+ */
+public class RemoteHttpCacheServlet
+    extends HttpServlet
+{
+    /** Don't change. */
+    private static final long serialVersionUID = 8752849397531933346L;
+
+    /** The Logger. */
+    private static final Log log = LogManager.getLog( RemoteHttpCacheServlet.class );
+
+    /** The cache manager */
+    private static CompositeCacheManager cacheMgr;
+
+    /** The service that does the work. */
+    private static ICacheServiceNonLocal<Serializable, Serializable> remoteCacheService;
+
+    /** This needs to be standard, since the other side is standard */
+    private final StandardSerializer serializer = new StandardSerializer();
+
+    /** Number of service calls. */
+    private int serviceCalls = 0;
+
+    /** The interval at which we will log the count. */
+    private final int logInterval = 100;
+
+    /**
+     * Initializes the cache.
+     * <p>
+     * This provides an easy extension point. Simply extend this servlet and override the init
+     * method to change the way the properties are loaded.
+     * @param config
+     * @throws ServletException
+     */
+    @Override
+    public void init( ServletConfig config )
+        throws ServletException
+    {
+        try
+        {
+            cacheMgr = CompositeCacheManager.getInstance();
+        }
+        catch (CacheException e)
+        {
+            throw new ServletException(e);
+        }
+
+        remoteCacheService = createRemoteHttpCacheService( cacheMgr );
+
+        super.init( config );
+    }
+
+    /**
+     * Read the request, call the processor, write the response.
+     * <p>
+     * @param request
+     * @param response
+     * @throws ServletException
+     * @throws IOException
+     */
+    @Override
+    public void service( HttpServletRequest request, HttpServletResponse response )
+        throws ServletException, IOException
+    {
+        incrementServiceCallCount();
+        log.debug( "Servicing a request. {0}", request );
+
+        RemoteCacheRequest<Serializable, Serializable> remoteRequest = readRequest( request );
+        RemoteCacheResponse<Object> cacheResponse = processRequest( remoteRequest );
+
+        writeResponse( response, cacheResponse );
+    }
+
+    /**
+     * Read the request from the input stream.
+     * <p>
+     * @param request
+     * @return RemoteHttpCacheRequest
+     */
+    protected RemoteCacheRequest<Serializable, Serializable> readRequest( HttpServletRequest request )
+    {
+        RemoteCacheRequest<Serializable, Serializable> remoteRequest = null;
+        try
+        {
+            InputStream inputStream = request.getInputStream();
+            log.debug( "After getting input stream and before reading it" );
+
+            remoteRequest = readRequestFromStream( inputStream );
+        }
+        catch ( Exception e )
+        {
+            log.error( "Could not get a RemoteHttpCacheRequest object from the input stream.", e );
+        }
+        return remoteRequest;
+    }
+
+    /**
+     * Reads the response from the stream and then closes it.
+     * <p>
+     * @param inputStream
+     * @return RemoteHttpCacheRequest
+     * @throws IOException
+     * @throws ClassNotFoundException
+     */
+    protected RemoteCacheRequest<Serializable, Serializable> readRequestFromStream( InputStream inputStream )
+        throws IOException, ClassNotFoundException
+    {
+        ObjectInputStream ois = new ObjectInputStreamClassLoaderAware( inputStream, null );
+
+        @SuppressWarnings("unchecked") // Need to cast from Object
+        RemoteCacheRequest<Serializable, Serializable> remoteRequest
+            = (RemoteCacheRequest<Serializable, Serializable>) ois.readObject();
+        ois.close();
+        return remoteRequest;
+    }
+
+    /**
+     * Write the response to the output stream.
+     * <p>
+     * @param response
+     * @param cacheResponse
+     */
+    protected void writeResponse( HttpServletResponse response, RemoteCacheResponse<Object> cacheResponse )
+    {
+        try
+        {
+            response.setContentType( "application/octet-stream" );
+
+            byte[] responseAsByteAray = serializer.serialize( cacheResponse );
+            response.setContentLength( responseAsByteAray.length );
+
+            OutputStream outputStream = response.getOutputStream();
+            log.debug( "Opened output stream.  Response size: {0}",
+                    () -> responseAsByteAray.length );
+            // WRITE
+            outputStream.write( responseAsByteAray );
+            outputStream.flush();
+            outputStream.close();
+        }
+        catch ( IOException e )
+        {
+            log.error( "Problem writing response. {0}", cacheResponse, e );
+        }
+    }
+
+    /**
+     * Processes the request. It will call the appropriate method on the service
+     * <p>
+     * @param request
+     * @return RemoteHttpCacheResponse, never null
+     */
+    protected RemoteCacheResponse<Object> processRequest( RemoteCacheRequest<Serializable, Serializable> request )
+    {
+        RemoteCacheResponse<Object> response = new RemoteCacheResponse<>();
+
+        if ( request == null )
+        {
+            String message = "The request is null. Cannot process";
+            log.warn( message );
+            response.setSuccess( false );
+            response.setErrorMessage( message );
+        }
+        else
+        {
+            try
+            {
+                switch ( request.getRequestType() )
+                {
+                    case GET:
+                        ICacheElement<Serializable, Serializable> element =
+                            remoteCacheService.get( request.getCacheName(), request.getKey(), request.getRequesterId() );
+                        response.setPayload(element);
+                        break;
+                    case GET_MULTIPLE:
+                        Map<Serializable, ICacheElement<Serializable, Serializable>> elementMap =
+                            remoteCacheService.getMultiple( request.getCacheName(), request.getKeySet(), request.getRequesterId() );
+                        if ( elementMap != null )
+                        {
+                            Map<Serializable, ICacheElement<Serializable, Serializable>> map = new HashMap<>();
+                            map.putAll(elementMap);
+                            response.setPayload(map);
+                        }
+                        break;
+                    case GET_MATCHING:
+                        Map<Serializable, ICacheElement<Serializable, Serializable>> elementMapMatching =
+                            remoteCacheService.getMatching( request.getCacheName(), request.getPattern(), request.getRequesterId() );
+                        if ( elementMapMatching != null )
+                        {
+                            Map<Serializable, ICacheElement<Serializable, Serializable>> map = new HashMap<>();
+                            map.putAll(elementMapMatching);
+                            response.setPayload(map);
+                        }
+                        break;
+                    case REMOVE:
+                        remoteCacheService.remove( request.getCacheName(), request.getKey(), request.getRequesterId() );
+                        break;
+                    case REMOVE_ALL:
+                        remoteCacheService.removeAll( request.getCacheName(), request.getRequesterId() );
+                        break;
+                    case UPDATE:
+                        remoteCacheService.update( request.getCacheElement(), request.getRequesterId() );
+                        break;
+                    case ALIVE_CHECK:
+                    case DISPOSE:
+                        response.setSuccess( true );
+                        // DO NOTHING
+                        break;
+                    case GET_KEYSET:
+                        Set<Serializable> keys = remoteCacheService.getKeySet( request.getCacheName() );
+                        response.setPayload( keys );
+                        break;
+                    default:
+                        String message = "Unknown event type.  Cannot process " + request;
+                        log.warn( message );
+                        response.setSuccess( false );
+                        response.setErrorMessage( message );
+                        break;
+                }
+            }
+            catch ( IOException e )
+            {
+                String message = "Problem processing request. " + request + " Error: " + e.getMessage();
+                log.error( message, e );
+                response.setSuccess( false );
+                response.setErrorMessage( message );
+            }
+        }
+
+        return response;
+    }
+
+    /**
+     * Configures the attributes and the event logger and constructs a service.
+     * <p>
+     * @param cacheManager
+     * @return RemoteHttpCacheService
+     */
+    protected <K, V> RemoteHttpCacheService<K, V> createRemoteHttpCacheService( ICompositeCacheManager cacheManager )
+    {
+        Properties props = cacheManager.getConfigurationProperties();
+        ICacheEventLogger cacheEventLogger = configureCacheEventLogger( props );
+        RemoteHttpCacheServerAttributes attributes = configureRemoteHttpCacheServerAttributes( props );
+
+        RemoteHttpCacheService<K, V> service = new RemoteHttpCacheService<>( cacheManager, attributes, cacheEventLogger );
+        log.info( "Created new RemoteHttpCacheService {0}", service );
+        return service;
+    }
+
+    /**
+     * Tries to get the event logger.
+     * <p>
+     * @param props
+     * @return ICacheEventLogger
+     */
+    protected ICacheEventLogger configureCacheEventLogger( Properties props )
+    {
+        ICacheEventLogger cacheEventLogger = AuxiliaryCacheConfigurator
+            .parseCacheEventLogger( props, IRemoteHttpCacheConstants.HTTP_CACHE_SERVER_PREFIX );
+
+        return cacheEventLogger;
+    }
+
+    /**
+     * Configure.
+     * <p>
+     * jcs.remotehttpcache.serverattributes.ATTRIBUTENAME=ATTRIBUTEVALUE
+     * <p>
+     * @param prop
+     * @return RemoteCacheServerAttributesconfigureRemoteCacheServerAttributes
+     */
+    protected RemoteHttpCacheServerAttributes configureRemoteHttpCacheServerAttributes( Properties prop )
+    {
+        RemoteHttpCacheServerAttributes rcsa = new RemoteHttpCacheServerAttributes();
+
+        // configure automatically
+        PropertySetter.setProperties( rcsa, prop,
+                                      IRemoteHttpCacheConstants.HTTP_CACHE_SERVER_ATTRIBUTES_PROPERTY_PREFIX + "." );
+
+        return rcsa;
+    }
+
+    /**
+     * @param rcs the remoteCacheService to set
+     */
+    protected void setRemoteCacheService(ICacheServiceNonLocal<Serializable, Serializable> rcs)
+    {
+        remoteCacheService = rcs;
+    }
+
+    /**
+     * Log some details.
+     */
+    private void incrementServiceCallCount()
+    {
+        // not thread safe, but it doesn't have to be accurate
+        serviceCalls++;
+        if ( log.isInfoEnabled() )
+        {
+            if ( serviceCalls % logInterval == 0 )
+            {
+                log.info( "serviceCalls = {0}", serviceCalls );
+            }
+        }
+    }
+
+    /** Release the cache manager. */
+    @Override
+    public void destroy()
+    {
+        log.info( "Servlet Destroyed, shutting down JCS." );
+
+        cacheMgr.shutDown();
+    }
+
+    /**
+     * Get servlet information
+     * <p>
+     * @return basic info
+     */
+    @Override
+    public String getServletInfo()
+    {
+        return "RemoteHttpCacheServlet";
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/package.html b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/package.html
similarity index 100%
rename from commons-jcs-core/src/main/java/org/apache/commons/jcs/auxiliary/remote/package.html
rename to commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/package.html
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/server/RegistryKeepAliveRunner.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/server/RegistryKeepAliveRunner.java
new file mode 100644
index 0000000..25febd0
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/server/RegistryKeepAliveRunner.java
@@ -0,0 +1,196 @@
+package org.apache.commons.jcs3.auxiliary.remote.server;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.rmi.Naming;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.rmi.registry.Registry;
+
+import org.apache.commons.jcs3.auxiliary.remote.RemoteUtils;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * This class tries to keep the registry alive. If if is able to create a registry, it will also
+ * rebind the remote cache server.
+ */
+public class RegistryKeepAliveRunner
+    implements Runnable
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog( RegistryKeepAliveRunner.class );
+
+    /** The URL of the service to look for. */
+    private final String namingURL;
+
+    /** The service name. */
+    private final String serviceName;
+
+    /** the port on which to start the registry */
+    private final int registryPort;
+
+    /** An optional event logger */
+    private ICacheEventLogger cacheEventLogger;
+
+    /** the registry */
+    private Registry registry;
+
+    /**
+     * @param registryHost - Hostname of the registry
+     * @param registryPort - the port on which to start the registry
+     * @param serviceName
+     */
+    public RegistryKeepAliveRunner( String registryHost, int registryPort, String serviceName )
+    {
+        this.namingURL = RemoteUtils.getNamingURL(registryHost, registryPort, serviceName);
+        this.serviceName = serviceName;
+        this.registryPort = registryPort;
+    }
+
+    /**
+     * Tries to lookup the server. If unsuccessful it will rebind the server using the factory
+     * rebind method.
+     * <p>
+     */
+    @Override
+    public void run()
+    {
+        checkAndRestoreIfNeeded();
+    }
+
+    /**
+     * Tries to lookup the server. If unsuccessful it will rebind the server using the factory
+     * rebind method.
+     */
+    protected void checkAndRestoreIfNeeded()
+    {
+        log.debug( "looking up server {0}", namingURL );
+
+        try
+        {
+            Object obj = Naming.lookup( namingURL );
+
+            // Successful connection to the remote server.
+            String message = "RMI registry looks fine.  Found [" + obj + "] in registry [" + namingURL + "]";
+            if ( cacheEventLogger != null )
+            {
+                cacheEventLogger.logApplicationEvent( "RegistryKeepAliveRunner", "Naming.lookup", message );
+            }
+            log.debug( message );
+        }
+        catch ( Exception ex )
+        {
+            // Failed to connect to the remote server.
+            String message = "Problem finding server at [" + namingURL
+                + "].  Will attempt to start registry and rebind.";
+            log.error( message, ex );
+            if ( cacheEventLogger != null )
+            {
+                cacheEventLogger.logError( "RegistryKeepAliveRunner", "Naming.lookup", message + ":" + ex.getMessage() );
+            }
+            createAndRegister( serviceName );
+        }
+    }
+
+    /**
+     * Creates the registry and registers the server.
+     * <p>
+     * @param serviceName the service name
+     */
+    protected void createAndRegister( String serviceName )
+    {
+        createReqistry( serviceName );
+        registerServer( serviceName );
+    }
+
+    /**
+     * Try to create the registry. Log errors
+     * <p>
+     * @param serviceName the service name
+     */
+    protected void createReqistry( String serviceName )
+    {
+        // TODO: Refactor method signature. This is ugly but required to keep the binary API compatibility
+        this.registry = RemoteUtils.createRegistry(registryPort);
+
+        if ( cacheEventLogger != null )
+        {
+            if (this.registry != null)
+            {
+                cacheEventLogger.logApplicationEvent( "RegistryKeepAliveRunner", "createRegistry",
+                        "Successfully created registry [" + serviceName + "]." );
+            }
+            else
+            {
+                cacheEventLogger.logError( "RegistryKeepAliveRunner", "createRegistry",
+                        "Could not start registry [" + serviceName + "]." );
+            }
+        }
+    }
+
+    /**
+     * Try to rebind the server.
+     * <p>
+     * @param serviceName the service name
+     */
+    protected void registerServer( String serviceName )
+    {
+        try
+        {
+            // try to rebind anyway
+            Remote server = RemoteCacheServerFactory.getRemoteCacheServer();
+
+            if ( server == null )
+            {
+                throw new RemoteException( "Cannot register the server until it is created." );
+            }
+
+            this.registry.rebind( serviceName, server );
+            String message = "Successfully rebound server to registry [" + serviceName + "].";
+            if ( cacheEventLogger != null )
+            {
+                cacheEventLogger.logApplicationEvent( "RegistryKeepAliveRunner", "registerServer", message );
+            }
+            log.info( message );
+        }
+        catch ( RemoteException e )
+        {
+            String message = "Could not rebind server to registry [" + serviceName + "].";
+            log.error( message, e );
+            if ( cacheEventLogger != null )
+            {
+                cacheEventLogger.logError( "RegistryKeepAliveRunner", "registerServer", message + ":"
+                    + e.getMessage() );
+            }
+        }
+    }
+
+    /**
+     * Allows it to be injected.
+     * <p>
+     * @param cacheEventLogger
+     */
+    public void setCacheEventLogger( ICacheEventLogger cacheEventLogger )
+    {
+        this.cacheEventLogger = cacheEventLogger;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/server/RemoteCacheServer.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/server/RemoteCacheServer.java
new file mode 100644
index 0000000..fb7f5a4
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/server/RemoteCacheServer.java
@@ -0,0 +1,1528 @@
+package org.apache.commons.jcs3.auxiliary.remote.server;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.rmi.RemoteException;
+import java.rmi.registry.Registry;
+import java.rmi.server.RMISocketFactory;
+import java.rmi.server.UnicastRemoteObject;
+import java.rmi.server.Unreferenced;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.commons.jcs3.access.exception.CacheException;
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheListener;
+import org.apache.commons.jcs3.auxiliary.remote.server.behavior.IRemoteCacheServer;
+import org.apache.commons.jcs3.auxiliary.remote.server.behavior.IRemoteCacheServerAttributes;
+import org.apache.commons.jcs3.auxiliary.remote.server.behavior.RemoteType;
+import org.apache.commons.jcs3.engine.CacheEventQueueFactory;
+import org.apache.commons.jcs3.engine.CacheListeners;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheEventQueue;
+import org.apache.commons.jcs3.engine.behavior.ICacheListener;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+import org.apache.commons.jcs3.engine.control.CompositeCacheManager;
+import org.apache.commons.jcs3.engine.logging.CacheEvent;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEvent;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.timing.ElapsedTimer;
+
+/**
+ * This class provides remote cache services. The remote cache server propagates events from local
+ * caches to other local caches. It can also store cached data, making it available to new clients.
+ * <p>
+ * Remote cache servers can be clustered. If the cache used by this remote cache is configured to
+ * use a remote cache of type cluster, the two remote caches will communicate with each other.
+ * Remote and put requests can be sent from one remote to another. If they are configured to
+ * broadcast such event to their client, then remove an puts can be sent to all locals in the
+ * cluster.
+ * <p>
+ * Get requests are made between clustered servers if AllowClusterGet is true. You can setup several
+ * clients to use one remote server and several to use another. The get local will be distributed
+ * between the two servers. Since caches are usually high get and low put, this should allow you to
+ * scale.
+ */
+public class RemoteCacheServer<K, V>
+    extends UnicastRemoteObject
+    implements IRemoteCacheServer<K, V>, Unreferenced
+{
+    public static final String DFEAULT_REMOTE_CONFIGURATION_FILE = "/remote.cache.ccf";
+
+    /** For serialization. Don't change. */
+    private static final long serialVersionUID = -8072345435941473116L;
+
+    /** log instance */
+    private static final Log log = LogManager.getLog( RemoteCacheServer.class );
+
+    /** Number of puts into the cache. */
+    private int puts = 0;
+
+    /** Maps cache name to CacheListeners object. association of listeners (regions). */
+    private final transient ConcurrentMap<String, CacheListeners<K, V>> cacheListenersMap =
+        new ConcurrentHashMap<>();
+
+    /** maps cluster listeners to regions. */
+    private final transient ConcurrentMap<String, CacheListeners<K, V>> clusterListenersMap =
+        new ConcurrentHashMap<>();
+
+    /** The central hub */
+    private transient CompositeCacheManager cacheManager;
+
+    /** relates listener id with a type */
+    private final ConcurrentMap<Long, RemoteType> idTypeMap = new ConcurrentHashMap<>();
+
+    /** relates listener id with an ip address */
+    private final ConcurrentMap<Long, String> idIPMap = new ConcurrentHashMap<>();
+
+    /** Used to get the next listener id. */
+    private final int[] listenerId = new int[1];
+
+    /** Configuration settings. */
+    // package protected for access by unit test code
+    final IRemoteCacheServerAttributes remoteCacheServerAttributes;
+
+    /** The interval at which we will log updates. */
+    private final int logInterval = 100;
+
+    /** An optional event logger */
+    private transient ICacheEventLogger cacheEventLogger;
+
+    /**
+     * Constructor for the RemoteCacheServer object. This initializes the server with the values
+     * from the properties object.
+     * <p>
+     * @param rcsa
+     * @param config cache hub configuration
+     * @throws RemoteException
+     */
+    protected RemoteCacheServer( IRemoteCacheServerAttributes rcsa, Properties config )
+        throws RemoteException
+    {
+        super( rcsa.getServicePort() );
+        this.remoteCacheServerAttributes = rcsa;
+        init( config );
+    }
+
+    /**
+     * Constructor for the RemoteCacheServer object. This initializes the server with the values
+     * from the properties object.
+     * <p>
+     * @param rcsa
+     * @param config cache hub configuration
+     * @param customRMISocketFactory
+     * @throws RemoteException
+     */
+    protected RemoteCacheServer( IRemoteCacheServerAttributes rcsa, Properties config, RMISocketFactory customRMISocketFactory )
+        throws RemoteException
+    {
+        super( rcsa.getServicePort(), customRMISocketFactory, customRMISocketFactory );
+        this.remoteCacheServerAttributes = rcsa;
+        init( config );
+    }
+
+    /**
+     * Initialize the RMI Cache Server from a properties object.
+     * <p>
+     * @param prop the configuration properties
+     * @throws RemoteException if the configuration of the cache manager instance fails
+     */
+    private void init( Properties prop ) throws RemoteException
+    {
+        try
+        {
+            cacheManager = createCacheManager( prop );
+        }
+        catch (CacheException e)
+        {
+            throw new RemoteException(e.getMessage(), e);
+        }
+
+        // cacheManager would have created a number of ICache objects.
+        // Use these objects to set up the cacheListenersMap.
+        cacheManager.getCacheNames().forEach(name -> {
+            CompositeCache<K, V> cache = cacheManager.getCache( name );
+            cacheListenersMap.put( name, new CacheListeners<>( cache ) );
+        });
+    }
+
+    /**
+     * Subclass can override this method to create the specific cache manager.
+     * <p>
+     * @param prop the configuration object.
+     * @return The cache hub configured with this configuration.
+     *
+     * @throws CacheException if the configuration cannot be loaded
+     */
+    private CompositeCacheManager createCacheManager( Properties prop ) throws CacheException
+    {
+        CompositeCacheManager hub = CompositeCacheManager.getUnconfiguredInstance();
+        hub.configure( prop );
+        return hub;
+    }
+
+    /**
+     * Puts a cache bean to the remote cache and notifies all listeners which <br>
+     * <ol>
+     * <li>have a different listener id than the originating host;</li>
+     * <li>are currently subscribed to the related cache.</li>
+     * </ol>
+     * <p>
+     * @param item
+     * @throws IOException
+     */
+    public void put( ICacheElement<K, V> item )
+        throws IOException
+    {
+        update( item );
+    }
+
+    /**
+     * @param item
+     * @throws IOException
+     */
+    @Override
+    public void update( ICacheElement<K, V> item )
+        throws IOException
+    {
+        update( item, 0 );
+    }
+
+    /**
+     * The internal processing is wrapped in event logging calls.
+     * <p>
+     * @param item
+     * @param requesterId
+     * @throws IOException
+     */
+    @Override
+    public void update( ICacheElement<K, V> item, long requesterId )
+        throws IOException
+    {
+        ICacheEvent<ICacheElement<K, V>> cacheEvent = createICacheEvent( item, requesterId, ICacheEventLogger.UPDATE_EVENT );
+        try
+        {
+            processUpdate( item, requesterId );
+        }
+        finally
+        {
+            logICacheEvent( cacheEvent );
+        }
+    }
+
+    /**
+     * An update can come from either a local cache's remote auxiliary, or it can come from a remote
+     * server. A remote server is considered a a source of type cluster.
+     * <p>
+     * If the update came from a cluster, then we should tell the cache manager that this was a
+     * remote put. This way, any lateral and remote auxiliaries configured for the region will not
+     * be updated. This is basically how a remote listener works when plugged into a local cache.
+     * <p>
+     * If the cluster is configured to keep local cluster consistency, then all listeners will be
+     * updated. This allows cluster server A to update cluster server B and then B to update its
+     * clients if it is told to keep local cluster consistency. Otherwise, server A will update
+     * server B and B will not tell its clients. If you cluster using lateral caches for instance,
+     * this is how it will work. Updates to a cluster node, will never get to the leaves. The remote
+     * cluster, with local cluster consistency, allows you to update leaves. This basically allows
+     * you to have a failover remote server.
+     * <p>
+     * Since currently a cluster will not try to get from other cluster servers, you can scale a bit
+     * with a cluster configuration. Puts and removes will be broadcasted to all clients, but the
+     * get load on a remote server can be reduced.
+     * <p>
+     * @param item
+     * @param requesterId
+     */
+    private void processUpdate( ICacheElement<K, V> item, long requesterId )
+    {
+        ElapsedTimer timer = new ElapsedTimer();
+        logUpdateInfo( item );
+
+        try
+        {
+            CacheListeners<K, V> cacheDesc = getCacheListeners( item.getCacheName() );
+            /* Object val = */item.getVal();
+
+            boolean fromCluster = isRequestFromCluster( requesterId );
+
+            log.debug( "In update, requesterId = [{0}] fromCluster = {1}", requesterId, fromCluster );
+
+            // ordered cache item update and notification.
+            synchronized ( cacheDesc )
+            {
+                try
+                {
+                    CompositeCache<K, V> c = (CompositeCache<K, V>) cacheDesc.cache;
+
+                    // If the source of this request was not from a cluster,
+                    // then consider it a local update. The cache manager will
+                    // try to
+                    // update all auxiliaries.
+                    //
+                    // This requires that two local caches not be connected to
+                    // two clustered remote caches. The failover runner will
+                    // have to make sure of this. ALos, the local cache needs
+                    // avoid updating this source. Will need to pass the source
+                    // id somehow. The remote cache should update all local
+                    // caches
+                    // but not update the cluster source. Cluster remote caches
+                    // should only be updated by the server and not the
+                    // RemoteCache.
+                    if ( fromCluster )
+                    {
+                        log.debug( "Put FROM cluster, NOT updating other auxiliaries for region. "
+                                + " requesterId [{0}]", requesterId );
+                        c.localUpdate( item );
+                    }
+                    else
+                    {
+                        log.debug( "Put NOT from cluster, updating other auxiliaries for region. "
+                                + " requesterId [{0}]", requesterId );
+                        c.update( item );
+                    }
+                }
+                catch ( IOException ce )
+                {
+                    // swallow
+                    log.info( "Exception caught updating item. requesterId [{0}]: {1}",
+                            requesterId, ce.getMessage() );
+                }
+
+                // UPDATE LOCALS IF A REQUEST COMES FROM A CLUSTER
+                // IF LOCAL CLUSTER CONSISTENCY IS CONFIGURED
+                if ( !fromCluster || ( fromCluster && remoteCacheServerAttributes.isLocalClusterConsistency() ) )
+                {
+                    ICacheEventQueue<K, V>[] qlist = getEventQList( cacheDesc, requesterId );
+                    log.debug( "qlist.length = {0}", qlist.length );
+                    for ( int i = 0; i < qlist.length; i++ )
+                    {
+                        qlist[i].addPutEvent( item );
+                    }
+                }
+            }
+        }
+        catch ( IOException e )
+        {
+            if ( cacheEventLogger != null )
+            {
+                cacheEventLogger.logError( "RemoteCacheServer", ICacheEventLogger.UPDATE_EVENT, e.getMessage()
+                    + " REGION: " + item.getCacheName() + " ITEM: " + item );
+            }
+
+            log.error( "Trouble in Update. requesterId [{0}]", requesterId, e );
+        }
+
+        // TODO use JAMON for timing
+        log.debug( "put took {0} ms.", () -> timer.getElapsedTime());
+    }
+
+    /**
+     * Log some details.
+     * <p>
+     * @param item
+     */
+    private void logUpdateInfo( ICacheElement<K, V> item )
+    {
+        // not thread safe, but it doesn't have to be 100% accurate
+        puts++;
+
+        if ( log.isInfoEnabled() )
+        {
+            if ( puts % logInterval == 0 )
+            {
+                log.info( "puts = {0}", puts );
+            }
+        }
+
+        log.debug( "In update, put [{0}] in [{1}]",
+                () -> item.getKey(), () -> item.getCacheName() );
+    }
+
+    /**
+     * Returns a cache value from the specified remote cache; or null if the cache or key does not
+     * exist.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @return ICacheElement
+     * @throws IOException
+     */
+    @Override
+    public ICacheElement<K, V> get( String cacheName, K key )
+        throws IOException
+    {
+        return this.get( cacheName, key, 0 );
+    }
+
+    /**
+     * Returns a cache bean from the specified cache; or null if the key does not exist.
+     * <p>
+     * Adding the requestor id, allows the cache to determine the source of the get.
+     * <p>
+     * The internal processing is wrapped in event logging calls.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @param requesterId
+     * @return ICacheElement
+     * @throws IOException
+     */
+    @Override
+    public ICacheElement<K, V> get( String cacheName, K key, long requesterId )
+        throws IOException
+    {
+        ICacheElement<K, V> element = null;
+        ICacheEvent<K> cacheEvent = createICacheEvent( cacheName, key, requesterId, ICacheEventLogger.GET_EVENT );
+        try
+        {
+            element = processGet( cacheName, key, requesterId );
+        }
+        finally
+        {
+            logICacheEvent( cacheEvent );
+        }
+        return element;
+    }
+
+    /**
+     * Returns a cache bean from the specified cache; or null if the key does not exist.
+     * <p>
+     * Adding the requester id, allows the cache to determine the source of the get.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @param requesterId
+     * @return ICacheElement
+     */
+    private ICacheElement<K, V> processGet( String cacheName, K key, long requesterId )
+    {
+        boolean fromCluster = isRequestFromCluster( requesterId );
+
+        log.debug( "get [{0}] from cache [{1}] requesterId = [{2}] fromCluster = {3}",
+                key, cacheName, requesterId, fromCluster );
+
+        CacheListeners<K, V> cacheDesc = getCacheListeners( cacheName );
+
+        ICacheElement<K, V> element = getFromCacheListeners( key, fromCluster, cacheDesc, null );
+        return element;
+    }
+
+    /**
+     * Gets the item from the associated cache listeners.
+     * <p>
+     * @param key
+     * @param fromCluster
+     * @param cacheDesc
+     * @param element
+     * @return ICacheElement
+     */
+    private ICacheElement<K, V> getFromCacheListeners( K key, boolean fromCluster, CacheListeners<K, V> cacheDesc,
+                                                 ICacheElement<K, V> element )
+    {
+        ICacheElement<K, V> returnElement = element;
+
+        if ( cacheDesc != null )
+        {
+            CompositeCache<K, V> c = (CompositeCache<K, V>) cacheDesc.cache;
+
+            // If we have a get come in from a client and we don't have the item
+            // locally, we will allow the cache to look in other non local sources,
+            // such as a remote cache or a lateral.
+            //
+            // Since remote servers never get from clients and clients never go
+            // remote from a remote call, this
+            // will not result in any loops.
+            //
+            // This is the only instance I can think of where we allow a remote get
+            // from a remote call. The purpose is to allow remote cache servers to
+            // talk to each other. If one goes down, you want it to be able to get
+            // data from those that were up when the failed server comes back o
+            // line.
+
+            if ( !fromCluster && this.remoteCacheServerAttributes.isAllowClusterGet() )
+            {
+                log.debug( "NonLocalGet. fromCluster [{0}] AllowClusterGet [{1}]",
+                        fromCluster, this.remoteCacheServerAttributes.isAllowClusterGet() );
+                returnElement = c.get( key );
+            }
+            else
+            {
+                // Gets from cluster type remote will end up here.
+                // Gets from all clients will end up here if allow cluster get is
+                // false.
+                log.debug( "LocalGet. fromCluster [{0}] AllowClusterGet [{1}]",
+                        fromCluster, this.remoteCacheServerAttributes.isAllowClusterGet() );
+                returnElement = c.localGet( key );
+            }
+        }
+
+        return returnElement;
+    }
+
+    /**
+     * Gets all matching items.
+     * <p>
+     * @param cacheName
+     * @param pattern
+     * @return Map of keys and wrapped objects
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern )
+        throws IOException
+    {
+        return getMatching( cacheName, pattern, 0 );
+    }
+
+    /**
+     * Retrieves all matching keys.
+     * <p>
+     * @param cacheName
+     * @param pattern
+     * @param requesterId
+     * @return Map of keys and wrapped objects
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern, long requesterId )
+        throws IOException
+    {
+        ICacheEvent<String> cacheEvent = createICacheEvent( cacheName, pattern, requesterId,
+                                                    ICacheEventLogger.GETMATCHING_EVENT );
+        try
+        {
+            return processGetMatching( cacheName, pattern, requesterId );
+        }
+        finally
+        {
+            logICacheEvent( cacheEvent );
+        }
+    }
+
+    /**
+     * Retrieves all matching keys.
+     * <p>
+     * @param cacheName
+     * @param pattern
+     * @param requesterId
+     * @return Map of keys and wrapped objects
+     */
+    protected Map<K, ICacheElement<K, V>> processGetMatching( String cacheName, String pattern, long requesterId )
+    {
+        boolean fromCluster = isRequestFromCluster( requesterId );
+
+        log.debug( "getMatching [{0}] from cache [{1}] requesterId = [{2}] fromCluster = {3}",
+                pattern, cacheName, requesterId, fromCluster );
+
+        CacheListeners<K, V> cacheDesc = null;
+        try
+        {
+            cacheDesc = getCacheListeners( cacheName );
+        }
+        catch ( Exception e )
+        {
+            log.error( "Problem getting listeners.", e );
+
+            if ( cacheEventLogger != null )
+            {
+                cacheEventLogger.logError( "RemoteCacheServer", ICacheEventLogger.GETMATCHING_EVENT, e.getMessage()
+                    + cacheName + " pattern: " + pattern );
+            }
+        }
+
+        return getMatchingFromCacheListeners( pattern, fromCluster, cacheDesc );
+    }
+
+    /**
+     * Gets the item from the associated cache listeners.
+     * <p>
+     * @param pattern
+     * @param fromCluster
+     * @param cacheDesc
+     * @return Map of keys to results
+     */
+    private Map<K, ICacheElement<K, V>> getMatchingFromCacheListeners( String pattern, boolean fromCluster, CacheListeners<K, V> cacheDesc )
+    {
+        Map<K, ICacheElement<K, V>> elements = null;
+        if ( cacheDesc != null )
+        {
+            CompositeCache<K, V> c = (CompositeCache<K, V>) cacheDesc.cache;
+
+            // We always want to go remote and then merge the items.  But this can lead to inconsistencies after
+            // failover recovery.  Removed items may show up.  There is no good way to prevent this.
+            // We should make it configurable.
+
+            if ( !fromCluster && this.remoteCacheServerAttributes.isAllowClusterGet() )
+            {
+                log.debug( "NonLocalGetMatching. fromCluster [{0}] AllowClusterGet [{1}]",
+                        fromCluster, this.remoteCacheServerAttributes.isAllowClusterGet() );
+                elements = c.getMatching( pattern );
+            }
+            else
+            {
+                // Gets from cluster type remote will end up here.
+                // Gets from all clients will end up here if allow cluster get is
+                // false.
+
+                log.debug( "LocalGetMatching. fromCluster [{0}] AllowClusterGet [{1}]",
+                        fromCluster, this.remoteCacheServerAttributes.isAllowClusterGet() );
+                elements = c.localGetMatching( pattern );
+            }
+        }
+        return elements;
+    }
+
+    /**
+     * Gets multiple items from the cache based on the given set of keys.
+     * <p>
+     * @param cacheName
+     * @param keys
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache for any of these keys
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys )
+        throws IOException
+    {
+        return this.getMultiple( cacheName, keys, 0 );
+    }
+
+    /**
+     * Gets multiple items from the cache based on the given set of keys.
+     * <p>
+     * The internal processing is wrapped in event logging calls.
+     * <p>
+     * @param cacheName
+     * @param keys
+     * @param requesterId
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache for any of these keys
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys, long requesterId )
+        throws IOException
+    {
+        ICacheEvent<Serializable> cacheEvent = createICacheEvent( cacheName, (Serializable) keys, requesterId,
+                                                    ICacheEventLogger.GETMULTIPLE_EVENT );
+        try
+        {
+            return processGetMultiple( cacheName, keys, requesterId );
+        }
+        finally
+        {
+            logICacheEvent( cacheEvent );
+        }
+    }
+
+    /**
+     * Gets multiple items from the cache based on the given set of keys.
+     * <p>
+     * @param cacheName
+     * @param keys
+     * @param requesterId
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache for any of these keys
+     */
+    private Map<K, ICacheElement<K, V>> processGetMultiple( String cacheName, Set<K> keys, long requesterId )
+    {
+        boolean fromCluster = isRequestFromCluster( requesterId );
+
+        log.debug( "getMultiple [{0}] from cache [{1}] requesterId = [{2}] fromCluster = {3}",
+                keys, cacheName, requesterId, fromCluster );
+
+        CacheListeners<K, V> cacheDesc = getCacheListeners( cacheName );
+        Map<K, ICacheElement<K, V>> elements = getMultipleFromCacheListeners( keys, null, fromCluster, cacheDesc );
+        return elements;
+    }
+
+    /**
+     * Since a non-receiving remote cache client will not register a listener, it will not have a
+     * listener id assigned from the server. As such the remote server cannot determine if it is a
+     * cluster or a normal client. It will assume that it is a normal client.
+     * <p>
+     * @param requesterId
+     * @return true is from a cluster.
+     */
+    private boolean isRequestFromCluster( long requesterId )
+    {
+        RemoteType remoteTypeL = idTypeMap.get( Long.valueOf( requesterId ) );
+        return remoteTypeL == RemoteType.CLUSTER;
+    }
+
+    /**
+     * Gets the items from the associated cache listeners.
+     * <p>
+     * @param keys
+     * @param elements
+     * @param fromCluster
+     * @param cacheDesc
+     * @return Map
+     */
+    private Map<K, ICacheElement<K, V>> getMultipleFromCacheListeners( Set<K> keys, Map<K, ICacheElement<K, V>> elements, boolean fromCluster, CacheListeners<K, V> cacheDesc )
+    {
+        Map<K, ICacheElement<K, V>> returnElements = elements;
+
+        if ( cacheDesc != null )
+        {
+            CompositeCache<K, V> c = (CompositeCache<K, V>) cacheDesc.cache;
+
+            // If we have a getMultiple come in from a client and we don't have the item
+            // locally, we will allow the cache to look in other non local sources,
+            // such as a remote cache or a lateral.
+            //
+            // Since remote servers never get from clients and clients never go
+            // remote from a remote call, this
+            // will not result in any loops.
+            //
+            // This is the only instance I can think of where we allow a remote get
+            // from a remote call. The purpose is to allow remote cache servers to
+            // talk to each other. If one goes down, you want it to be able to get
+            // data from those that were up when the failed server comes back on
+            // line.
+
+            if ( !fromCluster && this.remoteCacheServerAttributes.isAllowClusterGet() )
+            {
+                log.debug( "NonLocalGetMultiple. fromCluster [{0}] AllowClusterGet [{1}]",
+                        fromCluster, this.remoteCacheServerAttributes.isAllowClusterGet() );
+
+                returnElements = c.getMultiple( keys );
+            }
+            else
+            {
+                // Gets from cluster type remote will end up here.
+                // Gets from all clients will end up here if allow cluster get is
+                // false.
+
+                log.debug( "LocalGetMultiple. fromCluster [{0}] AllowClusterGet [{1}]",
+                        fromCluster, this.remoteCacheServerAttributes.isAllowClusterGet() );
+
+                returnElements = c.localGetMultiple( keys );
+            }
+        }
+
+        return returnElements;
+    }
+
+    /**
+     * Return the keys in the cache.
+     * <p>
+     * @param cacheName the name of the cache region
+     * @see org.apache.commons.jcs3.auxiliary.AuxiliaryCache#getKeySet()
+     */
+    @Override
+    public Set<K> getKeySet(String cacheName) throws IOException
+    {
+        return processGetKeySet( cacheName );
+    }
+
+    /**
+     * Gets the set of keys of objects currently in the cache.
+     * <p>
+     * @param cacheName
+     * @return Set
+     */
+    protected Set<K> processGetKeySet( String cacheName )
+    {
+        CacheListeners<K, V> cacheDesc = getCacheListeners( cacheName );
+
+        if ( cacheDesc == null )
+        {
+            return Collections.emptySet();
+        }
+
+        CompositeCache<K, V> c = (CompositeCache<K, V>) cacheDesc.cache;
+        return c.getKeySet();
+    }
+
+    /**
+     * Removes the given key from the specified remote cache. Defaults the listener id to 0.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @throws IOException
+     */
+    @Override
+    public void remove( String cacheName, K key )
+        throws IOException
+    {
+        remove( cacheName, key, 0 );
+    }
+
+    /**
+     * Remove the key from the cache region and don't tell the source listener about it.
+     * <p>
+     * The internal processing is wrapped in event logging calls.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @param requesterId
+     * @throws IOException
+     */
+    @Override
+    public void remove( String cacheName, K key, long requesterId )
+        throws IOException
+    {
+        ICacheEvent<K> cacheEvent = createICacheEvent( cacheName, key, requesterId, ICacheEventLogger.REMOVE_EVENT );
+        try
+        {
+            processRemove( cacheName, key, requesterId );
+        }
+        finally
+        {
+            logICacheEvent( cacheEvent );
+        }
+    }
+
+    /**
+     * Remove the key from the cache region and don't tell the source listener about it.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @param requesterId
+     * @throws IOException
+     */
+    private void processRemove( String cacheName, K key, long requesterId )
+        throws IOException
+    {
+        log.debug( "remove [{0}] from cache [{1}]", key, cacheName );
+
+        CacheListeners<K, V> cacheDesc = cacheListenersMap.get( cacheName );
+
+        boolean fromCluster = isRequestFromCluster( requesterId );
+
+        if ( cacheDesc != null )
+        {
+            // best attempt to achieve ordered cache item removal and
+            // notification.
+            synchronized ( cacheDesc )
+            {
+                boolean removeSuccess = false;
+
+                // No need to notify if it was not cached.
+                CompositeCache<K, V> c = (CompositeCache<K, V>) cacheDesc.cache;
+
+                if ( fromCluster )
+                {
+                    log.debug( "Remove FROM cluster, NOT updating other auxiliaries for region" );
+                    removeSuccess = c.localRemove( key );
+                }
+                else
+                {
+                    log.debug( "Remove NOT from cluster, updating other auxiliaries for region" );
+                    removeSuccess = c.remove( key );
+                }
+
+                log.debug( "remove [{0}] from cache [{1}] success (was it found) = {2}",
+                        key, cacheName, removeSuccess );
+
+                // UPDATE LOCALS IF A REQUEST COMES FROM A CLUSTER
+                // IF LOCAL CLUSTER CONSISTENCY IS CONFIGURED
+                if ( !fromCluster || ( fromCluster && remoteCacheServerAttributes.isLocalClusterConsistency() ) )
+                {
+                    ICacheEventQueue<K, V>[] qlist = getEventQList( cacheDesc, requesterId );
+
+                    for ( int i = 0; i < qlist.length; i++ )
+                    {
+                        qlist[i].addRemoveEvent( key );
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Remove all keys from the specified remote cache.
+     * <p>
+     * @param cacheName
+     * @throws IOException
+     */
+    @Override
+    public void removeAll( String cacheName )
+        throws IOException
+    {
+        removeAll( cacheName, 0 );
+    }
+
+    /**
+     * Remove all keys from the specified remote cache.
+     * <p>
+     * The internal processing is wrapped in event logging calls.
+     * <p>
+     * @param cacheName
+     * @param requesterId
+     * @throws IOException
+     */
+    @Override
+    public void removeAll( String cacheName, long requesterId )
+        throws IOException
+    {
+        ICacheEvent<String> cacheEvent = createICacheEvent( cacheName, "all", requesterId, ICacheEventLogger.REMOVEALL_EVENT );
+        try
+        {
+            processRemoveAll( cacheName, requesterId );
+        }
+        finally
+        {
+            logICacheEvent( cacheEvent );
+        }
+    }
+
+    /**
+     * Remove all keys from the specified remote cache.
+     * <p>
+     * @param cacheName
+     * @param requesterId
+     * @throws IOException
+     */
+    private void processRemoveAll( String cacheName, long requesterId )
+        throws IOException
+    {
+        CacheListeners<K, V> cacheDesc = cacheListenersMap.get( cacheName );
+
+        boolean fromCluster = isRequestFromCluster( requesterId );
+
+        if ( cacheDesc != null )
+        {
+            // best attempt to achieve ordered cache item removal and
+            // notification.
+            synchronized ( cacheDesc )
+            {
+                // No need to broadcast, or notify if it was not cached.
+                CompositeCache<K, V> c = (CompositeCache<K, V>) cacheDesc.cache;
+
+                if ( fromCluster )
+                {
+                    log.debug( "RemoveALL FROM cluster, NOT updating other auxiliaries for region" );
+                    c.localRemoveAll();
+                }
+                else
+                {
+                    log.debug( "RemoveALL NOT from cluster, updating other auxiliaries for region" );
+                    c.removeAll();
+                }
+
+                // update registered listeners
+                if ( !fromCluster || ( fromCluster && remoteCacheServerAttributes.isLocalClusterConsistency() ) )
+                {
+                    ICacheEventQueue<K, V>[] qlist = getEventQList( cacheDesc, requesterId );
+
+                    for ( int i = 0; i < qlist.length; i++ )
+                    {
+                        qlist[i].addRemoveAllEvent();
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * How many put events have we received.
+     * <p>
+     * @return puts
+     */
+    // Currently only intended for use by unit tests
+    int getPutCount()
+    {
+        return puts;
+    }
+
+    /**
+     * Frees the specified remote cache.
+     * <p>
+     * @param cacheName
+     * @throws IOException
+     */
+    @Override
+    public void dispose( String cacheName )
+        throws IOException
+    {
+        dispose( cacheName, 0 );
+    }
+
+    /**
+     * Frees the specified remote cache.
+     * <p>
+     * @param cacheName
+     * @param requesterId
+     * @throws IOException
+     */
+    public void dispose( String cacheName, long requesterId )
+        throws IOException
+    {
+        ICacheEvent<String> cacheEvent = createICacheEvent( cacheName, "none", requesterId, ICacheEventLogger.DISPOSE_EVENT );
+        try
+        {
+            processDispose( cacheName, requesterId );
+        }
+        finally
+        {
+            logICacheEvent( cacheEvent );
+        }
+    }
+
+    /**
+     * @param cacheName
+     * @param requesterId
+     * @throws IOException
+     */
+    private void processDispose( String cacheName, long requesterId )
+        throws IOException
+    {
+        log.info( "Dispose request received from listener [{0}]", requesterId );
+
+        CacheListeners<K, V> cacheDesc = cacheListenersMap.get( cacheName );
+
+        // this is dangerous
+        if ( cacheDesc != null )
+        {
+            // best attempt to achieve ordered free-cache-op and notification.
+            synchronized ( cacheDesc )
+            {
+                ICacheEventQueue<K, V>[] qlist = getEventQList( cacheDesc, requesterId );
+
+                for ( int i = 0; i < qlist.length; i++ )
+                {
+                    qlist[i].addDisposeEvent();
+                }
+                cacheManager.freeCache( cacheName );
+            }
+        }
+    }
+
+    /**
+     * Frees all remote caches.
+     * <p>
+     * @throws IOException
+     */
+    @Override
+    public void release()
+        throws IOException
+    {
+        for (CacheListeners<K, V> cacheDesc : cacheListenersMap.values())
+        {
+            ICacheEventQueue<K, V>[] qlist = getEventQList( cacheDesc, 0 );
+
+            for ( int i = 0; i < qlist.length; i++ )
+            {
+                qlist[i].addDisposeEvent();
+            }
+        }
+        cacheManager.release();
+    }
+
+    /**
+     * Returns the cache listener for the specified cache. Creates the cache and the cache
+     * descriptor if they do not already exist.
+     * <p>
+     * @param cacheName
+     * @return The cacheListeners value
+     */
+    protected CacheListeners<K, V> getCacheListeners( String cacheName )
+    {
+        CacheListeners<K, V> cacheListeners = cacheListenersMap.computeIfAbsent(cacheName, key -> {
+            CompositeCache<K, V> cache = cacheManager.getCache(key);
+            return new CacheListeners<>( cache );
+        });
+
+        return cacheListeners;
+    }
+
+    /**
+     * Gets the clusterListeners attribute of the RemoteCacheServer object.
+     * <p>
+     * TODO may be able to remove this
+     * @param cacheName
+     * @return The clusterListeners value
+     */
+    protected CacheListeners<K, V> getClusterListeners( String cacheName )
+    {
+        CacheListeners<K, V> cacheListeners = clusterListenersMap.computeIfAbsent(cacheName, key -> {
+            CompositeCache<K, V> cache = cacheManager.getCache( cacheName );
+            return new CacheListeners<>( cache );
+        });
+
+        return cacheListeners;
+    }
+
+    /**
+     * Gets the eventQList attribute of the RemoteCacheServer object. This returns the event queues
+     * stored in the cacheListeners object for a particular region, if the queue is not for this
+     * requester.
+     * <p>
+     * Basically, this makes sure that a request from a particular local cache, identified by its
+     * listener id, does not result in a call to that same listener.
+     * <p>
+     * @param cacheListeners
+     * @param requesterId
+     * @return The eventQList value
+     */
+    @SuppressWarnings("unchecked") // No generic arrays in java
+    private ICacheEventQueue<K, V>[] getEventQList( CacheListeners<K, V> cacheListeners, long requesterId )
+    {
+        ICacheEventQueue<K, V>[] list = cacheListeners.eventQMap.values().toArray( new ICacheEventQueue[0] );
+        int count = 0;
+        // Set those not qualified to null; Count those qualified.
+        for ( int i = 0; i < list.length; i++ )
+        {
+            ICacheEventQueue<K, V> q = list[i];
+            if ( q.isWorking() && q.getListenerId() != requesterId )
+            {
+                count++;
+            }
+            else
+            {
+                list[i] = null;
+            }
+        }
+        if ( count == list.length )
+        {
+            // All qualified.
+            return list;
+        }
+
+        // Returns only the qualified.
+        ICacheEventQueue<K, V>[] qq = new ICacheEventQueue[count];
+        count = 0;
+        for ( int i = 0; i < list.length; i++ )
+        {
+            if ( list[i] != null )
+            {
+                qq[count++] = list[i];
+            }
+        }
+        return qq;
+    }
+
+    /**
+     * Removes dead event queues. Should clean out deregistered listeners.
+     * <p>
+     * @param eventQMap
+     */
+    private static <KK, VV> void cleanupEventQMap( Map<Long, ICacheEventQueue<KK, VV>> eventQMap )
+    {
+        // this does not care if the q is alive (i.e. if
+        // there are active threads; it cares if the queue
+        // is working -- if it has not encountered errors
+        // above the failure threshold
+        eventQMap.entrySet().removeIf(e -> !e.getValue().isWorking());
+    }
+
+    /**
+     * Subscribes to the specified remote cache.
+     * <p>
+     * If the client id is 0, then the remote cache server will increment it's local count and
+     * assign an id to the client.
+     * <p>
+     * @param cacheName the specified remote cache.
+     * @param listener object to notify for cache changes. must be synchronized since there are
+     *            remote calls involved.
+     * @throws IOException
+     */
+    @Override
+    @SuppressWarnings("unchecked") // Need to cast to specific return type from getClusterListeners()
+    public <KK, VV> void addCacheListener( String cacheName, ICacheListener<KK, VV> listener )
+        throws IOException
+    {
+        if ( cacheName == null || listener == null )
+        {
+            throw new IllegalArgumentException( "cacheName and listener must not be null" );
+        }
+        CacheListeners<KK, VV> cacheListeners;
+
+        IRemoteCacheListener<KK, VV> ircl = (IRemoteCacheListener<KK, VV>) listener;
+
+        String listenerAddress = ircl.getLocalHostAddress();
+
+        RemoteType remoteType = ircl.getRemoteType();
+        if ( remoteType == RemoteType.CLUSTER )
+        {
+            log.debug( "adding cluster listener, listenerAddress [{0}]", listenerAddress );
+            cacheListeners = (CacheListeners<KK, VV>)getClusterListeners( cacheName );
+        }
+        else
+        {
+            log.debug( "adding normal listener, listenerAddress [{0}]", listenerAddress );
+            cacheListeners = (CacheListeners<KK, VV>)getCacheListeners( cacheName );
+        }
+        Map<Long, ICacheEventQueue<KK, VV>> eventQMap = cacheListeners.eventQMap;
+        cleanupEventQMap( eventQMap );
+
+        // synchronized ( listenerId )
+        synchronized ( ICacheListener.class )
+        {
+            long id = 0;
+            try
+            {
+                id = listener.getListenerId();
+                // clients probably shouldn't do this.
+                if ( id == 0 )
+                {
+                    // must start at one so the next gets recognized
+                    long listenerIdB = nextListenerId();
+                    log.debug( "listener id={0} addded for cache [{1}], listenerAddress [{2}]",
+                            listenerIdB & 0xff, cacheName, listenerAddress );
+                    listener.setListenerId( listenerIdB );
+                    id = listenerIdB;
+
+                    // in case it needs synchronization
+                    String message = "Adding vm listener under new id = [" + listenerIdB + "], listenerAddress ["
+                        + listenerAddress + "]";
+                    logApplicationEvent( "RemoteCacheServer", "addCacheListener", message );
+                    log.info( message );
+                }
+                else
+                {
+                    String message = "Adding listener under existing id = [" + id + "], listenerAddress ["
+                        + listenerAddress + "]";
+                    logApplicationEvent( "RemoteCacheServer", "addCacheListener", message );
+                    log.info( message );
+                    // should confirm the the host is the same as we have on
+                    // record, just in case a client has made a mistake.
+                }
+
+                // relate the type to an id
+                this.idTypeMap.put( Long.valueOf( id ), remoteType);
+                if ( listenerAddress != null )
+                {
+                    this.idIPMap.put( Long.valueOf( id ), listenerAddress );
+                }
+            }
+            catch ( IOException ioe )
+            {
+                String message = "Problem setting listener id, listenerAddress [" + listenerAddress + "]";
+                log.error( message, ioe );
+
+                if ( cacheEventLogger != null )
+                {
+                    cacheEventLogger.logError( "RemoteCacheServer", "addCacheListener", message + " - "
+                        + ioe.getMessage() );
+                }
+            }
+
+            CacheEventQueueFactory<KK, VV> fact = new CacheEventQueueFactory<>();
+            ICacheEventQueue<KK, VV> q = fact.createCacheEventQueue( listener, id, cacheName, remoteCacheServerAttributes
+                .getEventQueuePoolName(), remoteCacheServerAttributes.getEventQueueType() );
+
+            eventQMap.put(Long.valueOf(listener.getListenerId()), q);
+
+            log.info( cacheListeners );
+        }
+    }
+
+    /**
+     * Subscribes to all remote caches.
+     * <p>
+     * @param listener The feature to be added to the CacheListener attribute
+     * @throws IOException
+     */
+    @Override
+    public <KK, VV> void addCacheListener( ICacheListener<KK, VV> listener )
+        throws IOException
+    {
+        for (String cacheName : cacheListenersMap.keySet())
+        {
+            addCacheListener( cacheName, listener );
+
+            log.debug( "Adding listener for cache [{0}]", cacheName );
+        }
+    }
+
+    /**
+     * Unsubscribe this listener from this region. If the listener is registered, it will be removed
+     * from the event queue map list.
+     * <p>
+     * @param cacheName
+     * @param listener
+     * @throws IOException
+     */
+    @Override
+    public <KK, VV> void removeCacheListener( String cacheName, ICacheListener<KK, VV> listener )
+        throws IOException
+    {
+        removeCacheListener( cacheName, listener.getListenerId() );
+    }
+
+    /**
+     * Unsubscribe this listener from this region. If the listener is registered, it will be removed
+     * from the event queue map list.
+     * <p>
+     * @param cacheName
+     * @param listenerId
+     */
+    public void removeCacheListener( String cacheName, long listenerId )
+    {
+        String message = "Removing listener for cache region = [" + cacheName + "] and listenerId [" + listenerId + "]";
+        logApplicationEvent( "RemoteCacheServer", "removeCacheListener", message );
+        log.info( message );
+
+        boolean isClusterListener = isRequestFromCluster( listenerId );
+
+        CacheListeners<K, V> cacheDesc = null;
+
+        if ( isClusterListener )
+        {
+            cacheDesc = getClusterListeners( cacheName );
+        }
+        else
+        {
+            cacheDesc = getCacheListeners( cacheName );
+        }
+        Map<Long, ICacheEventQueue<K, V>> eventQMap = cacheDesc.eventQMap;
+        cleanupEventQMap( eventQMap );
+        ICacheEventQueue<K, V> q = eventQMap.remove( Long.valueOf( listenerId ) );
+
+        if ( q != null )
+        {
+            log.debug( "Found queue for cache region = [{0}] and listenerId [{1}]",
+                    cacheName, listenerId );
+            q.destroy();
+            cleanupEventQMap( eventQMap );
+        }
+        else
+        {
+            log.debug( "Did not find queue for cache region = [{0}] and listenerId [{1}]",
+                    cacheName, listenerId );
+        }
+
+        // cleanup
+        idTypeMap.remove( Long.valueOf( listenerId ) );
+        idIPMap.remove( Long.valueOf( listenerId ) );
+
+        log.info( "After removing listener [{0}] cache region {1} listener size [{2}]",
+                listenerId, cacheName, eventQMap.size() );
+    }
+
+    /**
+     * Unsubscribes from all remote caches.
+     * <p>
+     * @param listener
+     * @throws IOException
+     */
+    @Override
+    public <KK, VV> void removeCacheListener( ICacheListener<KK, VV> listener )
+        throws IOException
+    {
+        for (String cacheName : cacheListenersMap.keySet())
+        {
+            removeCacheListener( cacheName, listener );
+
+            log.info( "Removing listener for cache [{0}]", cacheName );
+        }
+    }
+
+    /**
+     * Shuts down the remote server.
+     * <p>
+     * @throws IOException
+     */
+    @Override
+    public void shutdown()
+        throws IOException
+    {
+        shutdown("", Registry.REGISTRY_PORT);
+    }
+
+    /**
+     * Shuts down a server at a particular host and port. Then it calls shutdown on the cache
+     * itself.
+     * <p>
+     * @param host
+     * @param port
+     * @throws IOException
+     */
+    @Override
+    public void shutdown( String host, int port )
+        throws IOException
+    {
+        log.info( "Received shutdown request. Shutting down server." );
+
+        synchronized (listenerId)
+        {
+            for (String cacheName : cacheListenersMap.keySet())
+            {
+                for (int i = 0; i <= listenerId[0]; i++)
+                {
+                    removeCacheListener( cacheName, i );
+                }
+
+                log.info( "Removing listener for cache [{0}]", cacheName );
+            }
+
+            cacheListenersMap.clear();
+            clusterListenersMap.clear();
+        }
+        RemoteCacheServerFactory.shutdownImpl( host, port );
+        this.cacheManager.shutDown();
+    }
+
+    /**
+     * Called by the RMI runtime sometime after the runtime determines that the reference list, the
+     * list of clients referencing the remote object, becomes empty.
+     */
+    // TODO: test out the DGC.
+    @Override
+    public void unreferenced()
+    {
+        log.info( "*** Server now unreferenced and subject to GC. ***" );
+    }
+
+    /**
+     * Returns the next generated listener id [0,255].
+     * <p>
+     * @return the listener id of a client. This should be unique for this server.
+     */
+    private long nextListenerId()
+    {
+        long id = 0;
+        if ( listenerId[0] == Integer.MAX_VALUE )
+        {
+            synchronized ( listenerId )
+            {
+                id = listenerId[0];
+                listenerId[0] = 0;
+                // TODO: record & check if the generated id is currently being
+                // used by a valid listener. Currently if the id wraps after
+                // Long.MAX_VALUE,
+                // we just assume it won't collide with an existing listener who
+                // is live.
+            }
+        }
+        else
+        {
+            synchronized ( listenerId )
+            {
+                id = ++listenerId[0];
+            }
+        }
+        return id;
+    }
+
+    /**
+     * Gets the stats attribute of the RemoteCacheServer object.
+     * <p>
+     * @return The stats value
+     * @throws IOException
+     */
+    @Override
+    public String getStats()
+        throws IOException
+    {
+        return cacheManager.getStats();
+    }
+
+    /**
+     * Logs an event if an event logger is configured.
+     * <p>
+     * @param item
+     * @param requesterId
+     * @param eventName
+     * @return ICacheEvent
+     */
+    private ICacheEvent<ICacheElement<K, V>> createICacheEvent( ICacheElement<K, V> item, long requesterId, String eventName )
+    {
+        if ( cacheEventLogger == null )
+        {
+            return new CacheEvent<>();
+        }
+        String ipAddress = getExtraInfoForRequesterId( requesterId );
+        return cacheEventLogger
+            .createICacheEvent( "RemoteCacheServer", item.getCacheName(), eventName, ipAddress, item );
+    }
+
+    /**
+     * Logs an event if an event logger is configured.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @param requesterId
+     * @param eventName
+     * @return ICacheEvent
+     */
+    private <T> ICacheEvent<T> createICacheEvent( String cacheName, T key, long requesterId, String eventName )
+    {
+        if ( cacheEventLogger == null )
+        {
+            return new CacheEvent<>();
+        }
+        String ipAddress = getExtraInfoForRequesterId( requesterId );
+        return cacheEventLogger.createICacheEvent( "RemoteCacheServer", cacheName, eventName, ipAddress, key );
+    }
+
+    /**
+     * Logs an event if an event logger is configured.
+     * <p>
+     * @param source
+     * @param eventName
+     * @param optionalDetails
+     */
+    protected void logApplicationEvent( String source, String eventName, String optionalDetails )
+    {
+        if ( cacheEventLogger != null )
+        {
+            cacheEventLogger.logApplicationEvent( source, eventName, optionalDetails );
+        }
+    }
+
+    /**
+     * Logs an event if an event logger is configured.
+     * <p>
+     * @param cacheEvent
+     */
+    protected <T> void logICacheEvent( ICacheEvent<T> cacheEvent )
+    {
+        if ( cacheEventLogger != null )
+        {
+            cacheEventLogger.logICacheEvent( cacheEvent );
+        }
+    }
+
+    /**
+     * Ip address for the client, if one is stored.
+     * <p>
+     * Protected for testing.
+     * <p>
+     * @param requesterId
+     * @return String
+     */
+    protected String getExtraInfoForRequesterId( long requesterId )
+    {
+        String ipAddress = idIPMap.get( Long.valueOf( requesterId ) );
+        return ipAddress;
+    }
+
+    /**
+     * Allows it to be injected.
+     * <p>
+     * @param cacheEventLogger
+     */
+    public void setCacheEventLogger( ICacheEventLogger cacheEventLogger )
+    {
+        this.cacheEventLogger = cacheEventLogger;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/server/RemoteCacheServerAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/server/RemoteCacheServerAttributes.java
new file mode 100644
index 0000000..b890d7c
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/server/RemoteCacheServerAttributes.java
@@ -0,0 +1,214 @@
+package org.apache.commons.jcs3.auxiliary.remote.server;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.auxiliary.remote.CommonRemoteCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.remote.server.behavior.IRemoteCacheServerAttributes;
+
+/**
+ * These attributes are used to configure the remote cache server.
+ */
+public class RemoteCacheServerAttributes
+    extends CommonRemoteCacheAttributes
+    implements IRemoteCacheServerAttributes
+{
+    /** Don't change */
+    private static final long serialVersionUID = -2741662082869155365L;
+
+    /** port the server will listen to */
+    private int servicePort = 0;
+
+    /** Can a cluster remote get from other remotes */
+    private boolean allowClusterGet = true;
+
+    /** The config file, the initialization is multistage. Remote cache then composite cache. */
+    private String configFileName = "";
+
+    /** Should we start the registry */
+    private final boolean DEFAULT_START_REGISTRY = true;
+
+    /** Should we start the registry */
+    private boolean startRegistry = DEFAULT_START_REGISTRY;
+
+    /** Should we try to keep the registry alive */
+    private final boolean DEFAULT_USE_REGISTRY_KEEP_ALIVE = true;
+
+    /** Should we try to keep the registry alive */
+    private boolean useRegistryKeepAlive = DEFAULT_USE_REGISTRY_KEEP_ALIVE;
+
+    /** The delay between runs */
+    private long registryKeepAliveDelayMillis = 15 * 1000;
+
+    /** Default constructor for the RemoteCacheAttributes object */
+    public RemoteCacheServerAttributes()
+    {
+        super();
+    }
+
+    /**
+     * Gets the localPort attribute of the RemoteCacheAttributes object
+     * <p>
+     * @return The localPort value
+     */
+    @Override
+    public int getServicePort()
+    {
+        return this.servicePort;
+    }
+
+    /**
+     * Sets the localPort attribute of the RemoteCacheAttributes object
+     * <p>
+     * @param p The new localPort value
+     */
+    @Override
+    public void setServicePort( int p )
+    {
+        this.servicePort = p;
+    }
+
+    /**
+     * Should gets from non-cluster clients be allowed to get from other remote auxiliaries.
+     * <p>
+     * @return The localClusterConsistency value
+     */
+    @Override
+    public boolean isAllowClusterGet()
+    {
+        return allowClusterGet;
+    }
+
+    /**
+     * Should we try to get from other cluster servers if we don't find the items locally.
+     * <p>
+     * @param r The new localClusterConsistency value
+     */
+    @Override
+    public void setAllowClusterGet( boolean r )
+    {
+        allowClusterGet = r;
+    }
+
+    /**
+     * Gets the ConfigFileName attribute of the IRemoteCacheAttributes object
+     * <p>
+     * @return The clusterServers value
+     */
+    @Override
+    public String getConfigFileName()
+    {
+        return configFileName;
+    }
+
+    /**
+     * Sets the ConfigFileName attribute of the IRemoteCacheAttributes object
+     * <p>
+     * @param s The new clusterServers value
+     */
+    @Override
+    public void setConfigFileName( String s )
+    {
+        configFileName = s;
+    }
+
+    /**
+     * Should we try to keep the registry alive
+     * <p>
+     * @param useRegistryKeepAlive the useRegistryKeepAlive to set
+     */
+    @Override
+    public void setUseRegistryKeepAlive( boolean useRegistryKeepAlive )
+    {
+        this.useRegistryKeepAlive = useRegistryKeepAlive;
+    }
+
+    /**
+     * Should we start the registry
+     * <p>
+     * @param startRegistry the startRegistry to set
+     * @deprecated Always true, to be removed
+     */
+    @Deprecated
+    @Override
+    public void setStartRegistry( boolean startRegistry )
+    {
+        this.startRegistry = startRegistry;
+    }
+
+    /**
+     * Should we start the registry
+     * <p>
+     * @return the startRegistry
+     * @deprecated Always true, to be removed
+     */
+    @Deprecated
+    @Override
+    public boolean isStartRegistry()
+    {
+        return startRegistry;
+    }
+
+    /**
+     * Should we try to keep the registry alive
+     * <p>
+     * @return the useRegistryKeepAlive
+     */
+    @Override
+    public boolean isUseRegistryKeepAlive()
+    {
+        return useRegistryKeepAlive;
+    }
+
+    /**
+     * @param registryKeepAliveDelayMillis the registryKeepAliveDelayMillis to set
+     */
+    @Override
+    public void setRegistryKeepAliveDelayMillis( long registryKeepAliveDelayMillis )
+    {
+        this.registryKeepAliveDelayMillis = registryKeepAliveDelayMillis;
+    }
+
+    /**
+     * @return the registryKeepAliveDelayMillis
+     */
+    @Override
+    public long getRegistryKeepAliveDelayMillis()
+    {
+        return registryKeepAliveDelayMillis;
+    }
+
+    /**
+     * @return String details
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder(super.toString());
+        buf.append( "\n servicePort = [" + this.getServicePort() + "]" );
+        buf.append( "\n allowClusterGet = [" + this.isAllowClusterGet() + "]" );
+        buf.append( "\n configFileName = [" + this.getConfigFileName() + "]" );
+        buf.append( "\n rmiSocketFactoryTimeoutMillis = [" + this.getRmiSocketFactoryTimeoutMillis() + "]" );
+        buf.append( "\n useRegistryKeepAlive = [" + this.isUseRegistryKeepAlive() + "]" );
+        buf.append( "\n registryKeepAliveDelayMillis = [" + this.getRegistryKeepAliveDelayMillis() + "]" );
+        buf.append( "\n eventQueueType = [" + this.getEventQueueType() + "]" );
+        buf.append( "\n eventQueuePoolName = [" + this.getEventQueuePoolName() + "]" );
+        return buf.toString();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/server/RemoteCacheServerFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/server/RemoteCacheServerFactory.java
new file mode 100644
index 0000000..9a1c85f
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/server/RemoteCacheServerFactory.java
@@ -0,0 +1,487 @@
+package org.apache.commons.jcs3.auxiliary.remote.server;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.rmi.Naming;
+import java.rmi.NotBoundException;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.rmi.registry.Registry;
+import java.rmi.server.RMISocketFactory;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.Properties;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheConfigurator;
+import org.apache.commons.jcs3.auxiliary.remote.RemoteUtils;
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheConstants;
+import org.apache.commons.jcs3.engine.behavior.ICacheServiceAdmin;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.config.OptionConverter;
+import org.apache.commons.jcs3.utils.config.PropertySetter;
+import org.apache.commons.jcs3.utils.threadpool.DaemonThreadFactory;
+
+/**
+ * Provides remote cache services. This creates remote cache servers and can proxy command line
+ * requests to a running server.
+ */
+public class RemoteCacheServerFactory
+    implements IRemoteCacheConstants
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog( RemoteCacheServerFactory.class );
+
+    /** The single instance of the RemoteCacheServer object. */
+    private static RemoteCacheServer<?, ?> remoteCacheServer;
+
+    /** The name of the service. */
+    private static String serviceName = IRemoteCacheConstants.REMOTE_CACHE_SERVICE_VAL;
+
+    /** Executes the registry keep alive. */
+    private static ScheduledExecutorService keepAliveDaemon;
+
+    /** A reference to the registry. */
+    private static Registry registry = null;
+
+    /** Constructor for the RemoteCacheServerFactory object. */
+    private RemoteCacheServerFactory()
+    {
+        super();
+    }
+
+    /**
+     * This will allow you to get stats from the server, etc. Perhaps we should provide methods on
+     * the factory to do this instead.
+     * <p>
+     * A remote cache is either a local cache or a cluster cache.
+     * </p>
+     * @return Returns the remoteCacheServer.
+     */
+    @SuppressWarnings("unchecked") // Need cast to specific RemoteCacheServer
+    public static <K, V> RemoteCacheServer<K, V> getRemoteCacheServer()
+    {
+        return (RemoteCacheServer<K, V>)remoteCacheServer;
+    }
+
+    // ///////////////////// Startup/shutdown methods. //////////////////
+    /**
+     * Starts up the remote cache server on this JVM, and binds it to the registry on the given host
+     * and port.
+     * <p>
+     * A remote cache is either a local cache or a cluster cache.
+     * <p>
+     * @param host
+     * @param port
+     * @param props
+     * @throws IOException
+     */
+    public static void startup( String host, int port, Properties props)
+        throws IOException
+    {
+        if ( remoteCacheServer != null )
+        {
+            throw new IllegalArgumentException( "Server already started." );
+        }
+
+        synchronized ( RemoteCacheServer.class )
+        {
+            if ( remoteCacheServer != null )
+            {
+                return;
+            }
+            if ( host == null )
+            {
+                host = "";
+            }
+
+            RemoteCacheServerAttributes rcsa = configureRemoteCacheServerAttributes(props);
+
+            // These should come from the file!
+            rcsa.setRemoteLocation( host, port );
+            log.info( "Creating server with these attributes: {0}", rcsa );
+
+            setServiceName( rcsa.getRemoteServiceName() );
+
+            RMISocketFactory customRMISocketFactory = configureObjectSpecificCustomFactory( props );
+
+            RemoteUtils.configureGlobalCustomSocketFactory( rcsa.getRmiSocketFactoryTimeoutMillis() );
+
+            // CONFIGURE THE EVENT LOGGER
+            ICacheEventLogger cacheEventLogger = configureCacheEventLogger( props );
+
+            // CREATE SERVER
+            if ( customRMISocketFactory != null )
+            {
+                remoteCacheServer = new RemoteCacheServer<>( rcsa, props, customRMISocketFactory );
+            }
+            else
+            {
+                remoteCacheServer = new RemoteCacheServer<>( rcsa, props );
+            }
+
+            remoteCacheServer.setCacheEventLogger( cacheEventLogger );
+
+            // START THE REGISTRY
+        	registry = RemoteUtils.createRegistry(port);
+
+            // REGISTER THE SERVER
+            registerServer( serviceName, remoteCacheServer );
+
+            // KEEP THE REGISTRY ALIVE
+            if ( rcsa.isUseRegistryKeepAlive() )
+            {
+                if ( keepAliveDaemon == null )
+                {
+                    keepAliveDaemon = Executors.newScheduledThreadPool(1,
+                            new DaemonThreadFactory("JCS-RemoteCacheServerFactory-"));
+                }
+                RegistryKeepAliveRunner runner = new RegistryKeepAliveRunner( host, port, serviceName );
+                runner.setCacheEventLogger( cacheEventLogger );
+                keepAliveDaemon.scheduleAtFixedRate(runner, 0, rcsa.getRegistryKeepAliveDelayMillis(), TimeUnit.MILLISECONDS);
+            }
+        }
+    }
+
+    /**
+     * Tries to get the event logger by new and old config styles.
+     * <p>
+     * @param props
+     * @return ICacheEventLogger
+     */
+    protected static ICacheEventLogger configureCacheEventLogger( Properties props )
+    {
+        ICacheEventLogger cacheEventLogger = AuxiliaryCacheConfigurator
+            .parseCacheEventLogger( props, IRemoteCacheConstants.CACHE_SERVER_PREFIX );
+
+        // try the old way
+        if ( cacheEventLogger == null )
+        {
+            cacheEventLogger = AuxiliaryCacheConfigurator.parseCacheEventLogger( props,
+                                                                                 IRemoteCacheConstants.PROPERTY_PREFIX );
+        }
+        return cacheEventLogger;
+    }
+
+    /**
+     * This configures an object specific custom factory. This will be configured for just this
+     * object in the registry. This can be null.
+     * <p>
+     * @param props
+     * @return RMISocketFactory
+     */
+    protected static RMISocketFactory configureObjectSpecificCustomFactory( Properties props )
+    {
+        RMISocketFactory customRMISocketFactory =
+            OptionConverter.instantiateByKey( props, CUSTOM_RMI_SOCKET_FACTORY_PROPERTY_PREFIX, null );
+
+        if ( customRMISocketFactory != null )
+        {
+            PropertySetter.setProperties( customRMISocketFactory, props, CUSTOM_RMI_SOCKET_FACTORY_PROPERTY_PREFIX
+                + "." );
+            log.info( "Will use server specific custom socket factory. {0}",
+                    customRMISocketFactory );
+        }
+        else
+        {
+            log.info( "No server specific custom socket factory defined." );
+        }
+        return customRMISocketFactory;
+    }
+
+    /**
+     * Registers the server with the registry. I broke this off because we might want to have code
+     * that will restart a dead registry. It will need to rebind the server.
+     * <p>
+     * @param serviceName the name of the service
+     * @param server the server object to bind
+     * @throws RemoteException
+     */
+    protected static void registerServer(String serviceName, Remote server )
+        throws RemoteException
+    {
+        if ( server == null )
+        {
+            throw new RemoteException( "Cannot register the server until it is created." );
+        }
+
+        if ( registry == null )
+        {
+            throw new RemoteException( "Cannot register the server: Registry is null." );
+        }
+
+        log.info( "Binding server to {0}", serviceName );
+
+        registry.rebind( serviceName, server );
+    }
+
+    /**
+     * Configure.
+     * <p>
+     * jcs.remotecache.serverattributes.ATTRIBUTENAME=ATTRIBUTEVALUE
+     * <p>
+     * @param prop
+     * @return RemoteCacheServerAttributesconfigureRemoteCacheServerAttributes
+     */
+    protected static RemoteCacheServerAttributes configureRemoteCacheServerAttributes( Properties prop )
+    {
+        RemoteCacheServerAttributes rcsa = new RemoteCacheServerAttributes();
+
+        // configure automatically
+        PropertySetter.setProperties( rcsa, prop, CACHE_SERVER_ATTRIBUTES_PROPERTY_PREFIX + "." );
+
+        configureManuallyIfValuesArePresent( prop, rcsa );
+
+        return rcsa;
+    }
+
+    /**
+     * This looks for the old config values.
+     * <p>
+     * @param prop
+     * @param rcsa
+     */
+    private static void configureManuallyIfValuesArePresent( Properties prop, RemoteCacheServerAttributes rcsa )
+    {
+        // DEPRECATED CONFIG
+        String servicePortStr = prop.getProperty( REMOTE_CACHE_SERVICE_PORT );
+        if ( servicePortStr != null )
+        {
+            try
+            {
+                int servicePort = Integer.parseInt( servicePortStr );
+                rcsa.setServicePort( servicePort );
+                log.debug( "Remote cache service uses port number {0}", servicePort );
+            }
+            catch ( NumberFormatException ignore )
+            {
+                log.debug( "Remote cache service port property {0}" +
+                    " not specified. An anonymous port will be used.", REMOTE_CACHE_SERVICE_PORT );
+            }
+        }
+
+        String socketTimeoutMillisStr = prop.getProperty( SOCKET_TIMEOUT_MILLIS );
+        if ( socketTimeoutMillisStr != null )
+        {
+            try
+            {
+                int rmiSocketFactoryTimeoutMillis = Integer.parseInt( socketTimeoutMillisStr );
+                rcsa.setRmiSocketFactoryTimeoutMillis( rmiSocketFactoryTimeoutMillis );
+                log.debug( "Remote cache socket timeout {0} ms.", rmiSocketFactoryTimeoutMillis );
+            }
+            catch ( NumberFormatException ignore )
+            {
+                log.debug( "Remote cache socket timeout property {0}" +
+                    " not specified. The default will be used.", SOCKET_TIMEOUT_MILLIS );
+            }
+        }
+
+        String lccStr = prop.getProperty( REMOTE_LOCAL_CLUSTER_CONSISTENCY );
+        if ( lccStr != null )
+        {
+            boolean lcc = Boolean.parseBoolean( lccStr );
+            rcsa.setLocalClusterConsistency( lcc );
+        }
+
+        String acgStr = prop.getProperty( REMOTE_ALLOW_CLUSTER_GET );
+        if ( acgStr != null )
+        {
+            boolean acg = Boolean.parseBoolean( lccStr );
+            rcsa.setAllowClusterGet( acg );
+        }
+
+        // Register the RemoteCacheServer remote object in the registry.
+        rcsa.setRemoteServiceName(
+                prop.getProperty( REMOTE_CACHE_SERVICE_NAME, REMOTE_CACHE_SERVICE_VAL ).trim() );
+    }
+
+    /**
+     * Unbinds the remote server.
+     * <p>
+     * @param host
+     * @param port
+     * @throws IOException
+     */
+    static void shutdownImpl( String host, int port )
+        throws IOException
+    {
+        synchronized ( RemoteCacheServer.class )
+        {
+            if ( remoteCacheServer == null )
+            {
+                return;
+            }
+            log.info( "Unbinding host={0}, port={1}, serviceName={2}",
+                    host, port, getServiceName() );
+            try
+            {
+                Naming.unbind( RemoteUtils.getNamingURL(host, port, getServiceName()) );
+            }
+            catch ( MalformedURLException ex )
+            {
+                // impossible case.
+                throw new IllegalArgumentException( ex.getMessage() + "; host=" + host + ", port=" + port
+                    + ", serviceName=" + getServiceName() );
+            }
+            catch ( NotBoundException ex )
+            {
+                // ignore.
+            }
+            remoteCacheServer.release();
+            remoteCacheServer = null;
+
+            // Shut down keepalive scheduler
+            if ( keepAliveDaemon != null )
+            {
+                keepAliveDaemon.shutdownNow();
+                keepAliveDaemon = null;
+            }
+
+            // Try to release registry
+            if (registry != null)
+            {
+            	UnicastRemoteObject.unexportObject(registry, true);
+            	registry = null;
+            }
+        }
+    }
+
+    /**
+     * Creates an local RMI registry on the default port, starts up the remote cache server, and
+     * binds it to the registry.
+     * <p>
+     * A remote cache is either a local cache or a cluster cache.
+     * <p>
+     * @param args The command line arguments
+     * @throws Exception
+     */
+    public static void main( String[] args )
+        throws Exception
+    {
+        Properties prop = args.length > 0 ? RemoteUtils.loadProps( args[args.length - 1] ) : new Properties();
+
+        int port;
+        try
+        {
+            port = Integer.parseInt( prop.getProperty( "registry.port" ) );
+        }
+        catch ( NumberFormatException ex )
+        {
+            port = Registry.REGISTRY_PORT;
+        }
+
+        // shutdown
+        if ( args.length > 0 && args[0].toLowerCase().indexOf( "-shutdown" ) != -1 )
+        {
+            try
+            {
+                ICacheServiceAdmin admin = lookupCacheServiceAdmin(prop, port);
+                admin.shutdown();
+            }
+            catch ( Exception ex )
+            {
+                log.error( "Problem calling shutdown.", ex );
+            }
+            log.debug( "done." );
+            System.exit( 0 );
+        }
+
+        // STATS
+        if ( args.length > 0 && args[0].toLowerCase().indexOf( "-stats" ) != -1 )
+        {
+            log.debug( "getting cache stats" );
+
+            try
+            {
+                ICacheServiceAdmin admin = lookupCacheServiceAdmin(prop, port);
+
+                try
+                {
+//                    System.out.println( admin.getStats().toString() );
+                    log.debug( admin.getStats() );
+                }
+                catch ( IOException es )
+                {
+                    log.error( es );
+                }
+            }
+            catch ( Exception ex )
+            {
+                log.error( "Problem getting stats.", ex );
+            }
+            log.debug( "done." );
+            System.exit( 0 );
+        }
+
+        // startup.
+        String host = prop.getProperty( "registry.host" );
+
+        if ( host == null || host.trim().equals( "" ) || host.trim().equals( "localhost" ) )
+        {
+            log.debug( "main> creating registry on the localhost" );
+            RemoteUtils.createRegistry( port );
+        }
+        log.debug( "main> starting up RemoteCacheServer" );
+        startup( host, port, prop);
+        log.debug( "main> done" );
+    }
+
+    /**
+     * Look up the remote cache service admin instance
+     *
+     * @param config the configuration properties
+     * @param port the local port
+     * @return the admin object instance
+     *
+     * @throws Exception if lookup fails
+     */
+    private static ICacheServiceAdmin lookupCacheServiceAdmin(Properties config, int port) throws Exception
+    {
+        String remoteServiceName = config.getProperty( REMOTE_CACHE_SERVICE_NAME, REMOTE_CACHE_SERVICE_VAL ).trim();
+        String registry = RemoteUtils.getNamingURL("", port, remoteServiceName);
+
+        log.debug( "looking up server {0}", registry );
+        Object obj = Naming.lookup( registry );
+        log.debug( "server found" );
+
+        return (ICacheServiceAdmin) obj;
+    }
+
+    /**
+     * @param serviceName the serviceName to set
+     */
+    protected static void setServiceName( String serviceName )
+    {
+        RemoteCacheServerFactory.serviceName = serviceName;
+    }
+
+    /**
+     * @return the serviceName
+     */
+    protected static String getServiceName()
+    {
+        return serviceName;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/server/RemoteCacheStartupServlet.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/server/RemoteCacheStartupServlet.java
new file mode 100644
index 0000000..d9966ae
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/server/RemoteCacheStartupServlet.java
@@ -0,0 +1,283 @@
+package org.apache.commons.jcs3.auxiliary.remote.server;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.UnknownHostException;
+import java.nio.charset.StandardCharsets;
+import java.util.Properties;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.jcs3.access.exception.CacheException;
+import org.apache.commons.jcs3.auxiliary.remote.RemoteUtils;
+import org.apache.commons.jcs3.engine.control.CompositeCacheManager;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.net.HostNameUtil;
+
+/**
+ * This servlet can be used to startup the JCS remote cache. It is easy to
+ * deploy the remote server in a tomcat base. This give you an easy way to
+ * monitor its activity.
+ * <p>
+ * <code>
+ *  servlet&gt;
+        &lt;servlet-name&gt;JCSRemoteCacheStartupServlet&lt;/servlet-name&gt;
+        &lt;servlet-class&gt;
+             org.apache.commons.jcs3.auxiliary.remote.server.RemoteCacheStartupServlet
+        &lt;/servlet-class&gt;
+        &lt;load-on-startup&gt;1&lt;/load-on-startup&gt;
+    &lt;/servlet&gt;
+
+
+    &lt;servlet-mapping&gt;
+        &lt;servlet-name&gt;JCSRemoteCacheStartupServlet&lt;/servlet-name&gt;
+        &lt;url-pattern&gt;/jcs&lt;/url-pattern&gt;
+    &lt;/servlet-mapping&gt;
+ * </code>
+ *
+ * @author Aaron Smuts
+ */
+public class RemoteCacheStartupServlet
+        extends HttpServlet
+{
+    /** Don't change */
+    private static final long serialVersionUID = 1L;
+
+    /** The logger */
+    private static final Log log = LogManager.getLog(RemoteCacheStartupServlet.class);
+
+    /** The default port to start the registry on. */
+    private static final int DEFAULT_REGISTRY_PORT = 1101;
+
+    /** properties file name */
+    private static final String DEFAULT_PROPS_FILE_NAME = "/cache.ccf";
+
+    /** properties file name, must set prior to calling get instance */
+    private String propsFileName = DEFAULT_PROPS_FILE_NAME;
+
+    /** Configuration properties */
+    private int registryPort = DEFAULT_REGISTRY_PORT;
+
+    /** Configuration properties */
+    private String registryHost = null;
+
+    /**
+     * Starts the registry and then tries to bind to it.
+     * <p>
+     * Gets the port from a props file. Uses the local host name for the
+     * registry host. Tries to start the registry, ignoring failure. Starts the
+     * server.
+     * <p>
+     *
+     * @throws ServletException
+     */
+    @Override
+    public void init()
+            throws ServletException
+    {
+        super.init();
+
+        loadInitParams();
+        Properties props = loadPropertiesFromFile();
+
+        if (registryHost == null)
+        {
+            // we will always use the local machine for the registry
+            try
+            {
+                registryHost = HostNameUtil.getLocalHostAddress();
+            }
+            catch (UnknownHostException e)
+            {
+                log.error("Could not get local address to use for the registry!", e);
+            }
+        }
+
+        log.debug("registryHost = [{0}]", registryHost);
+
+        if ("localhost".equals(registryHost) || "127.0.0.1".equals(registryHost))
+        {
+            log.warn("The local address [{0}] is INVALID. Other machines must "
+                    + "be able to use the address to reach this server.", registryHost);
+        }
+
+        try
+        {
+            if (props == null)
+            {
+                throw new ServletException("Could not load configuration from " + propsFileName);
+            }
+
+            RemoteCacheServerFactory.startup(registryHost, registryPort, props);
+            log.info("Remote JCS Server started with properties from {0}", propsFileName);
+        }
+        catch (IOException e)
+        {
+            throw new ServletException("Problem starting remote cache server.", e);
+        }
+    }
+
+    /**
+     * It just dumps the stats.
+     * <p>
+     *
+     * @param request
+     * @param response
+     * @throws ServletException
+     * @throws IOException
+     */
+    @Override
+    protected void service(HttpServletRequest request, HttpServletResponse response)
+            throws ServletException, IOException
+    {
+        String stats = "";
+
+        try
+        {
+            stats = CompositeCacheManager.getInstance().getStats();
+        }
+        catch (CacheException e)
+        {
+            throw new ServletException(e);
+        }
+
+        log.info(stats);
+
+        try
+        {
+            String characterEncoding = response.getCharacterEncoding();
+            if (characterEncoding == null)
+            {
+                characterEncoding = StandardCharsets.UTF_8.name();
+                response.setCharacterEncoding(characterEncoding);
+            }
+            OutputStream os = response.getOutputStream();
+            os.write(stats.getBytes(characterEncoding));
+            os.close();
+        }
+        catch (IOException e)
+        {
+            log.error("Problem writing response.", e);
+        }
+    }
+
+    /**
+     * shuts the cache down.
+     */
+    @Override
+    public void destroy()
+    {
+        super.destroy();
+
+        log.info("Shutting down remote cache ");
+
+        try
+        {
+            RemoteCacheServerFactory.shutdownImpl(registryHost, registryPort);
+        }
+        catch (IOException e)
+        {
+            log.error("Problem shutting down.", e);
+        }
+
+        try
+        {
+            CompositeCacheManager.getInstance().shutDown();
+        }
+        catch (CacheException e)
+        {
+            log.error("Could not retrieve cache manager instance", e);
+        }
+    }
+
+    /**
+     * Load configuration values from config file if possible
+     */
+    private Properties loadPropertiesFromFile()
+    {
+        Properties props = null;
+
+        try
+        {
+            props = RemoteUtils.loadProps(propsFileName);
+            if (props != null)
+            {
+                registryHost = props.getProperty("registry.host", registryHost);
+                String portS = props.getProperty("registry.port", String.valueOf(registryPort));
+                setRegistryPort(portS);
+            }
+        }
+        catch (IOException e)
+        {
+            log.error("Problem loading props.", e);
+        }
+
+        return props;
+    }
+
+    /**
+     * Load configuration values from init params if possible
+     */
+    private void loadInitParams()
+    {
+        ServletConfig config = getServletConfig();
+        String _propsFileName = config.getInitParameter("propsFileName");
+        if (null != _propsFileName)
+        {
+            this.propsFileName = _propsFileName;
+        }
+        String _registryHost = config.getInitParameter("registryHost");
+        if (null != _registryHost)
+        {
+            this.registryHost = _registryHost;
+        }
+        String regPortString = config.getInitParameter("registryPort");
+        if (null != regPortString)
+        {
+            setRegistryPort(regPortString);
+        }
+    }
+
+    /**
+     * Set registry port from string If the string cannot be parsed, the default
+     * value is used
+     *
+     * @param portS
+     */
+    private void setRegistryPort(String portS)
+    {
+        try
+        {
+            this.registryPort = Integer.parseInt(portS);
+        }
+        catch (NumberFormatException e)
+        {
+            log.error("Problem converting port to an int.", e);
+            this.registryPort = DEFAULT_REGISTRY_PORT;
+        }
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/server/TimeoutConfigurableRMISocketFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/server/TimeoutConfigurableRMISocketFactory.java
new file mode 100644
index 0000000..8ca5b73
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/server/TimeoutConfigurableRMISocketFactory.java
@@ -0,0 +1,111 @@
+package org.apache.commons.jcs3.auxiliary.remote.server;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.rmi.server.RMISocketFactory;
+
+/**
+ * This can be injected into the the remote cache server as follows:
+ *
+ * <pre>
+ * jcs.remotecache.customrmisocketfactory=org.apache.commons.jcs3.auxiliary.remote.server.TimeoutConfigurableRMISocketFactory
+ * jcs.remotecache.customrmisocketfactory.readTimeout=5000
+ * jcs.remotecache.customrmisocketfactory.openTimeout=5000
+ * </pre>
+ */
+public class TimeoutConfigurableRMISocketFactory
+    extends RMISocketFactory
+    implements Serializable
+{
+    /** Don't change. */
+    private static final long serialVersionUID = 1489909775271203334L;
+
+    /** The socket read timeout */
+    private int readTimeout = 5000;
+
+    /** The socket open timeout */
+    private int openTimeout = 5000;
+
+    /**
+     * @param port
+     * @return ServerSocket
+     * @throws IOException
+     */
+    @Override
+    public ServerSocket createServerSocket( int port )
+        throws IOException
+    {
+        return new ServerSocket( port );
+    }
+
+    /**
+     * @param host
+     * @param port
+     * @return Socket
+     * @throws IOException
+     */
+    @Override
+    public Socket createSocket( String host, int port )
+        throws IOException
+    {
+        Socket socket = new Socket();
+        socket.setSoTimeout( readTimeout );
+        socket.setSoLinger( false, 0 );
+        socket.connect( new InetSocketAddress( host, port ), openTimeout );
+        return socket;
+    }
+
+    /**
+     * @param readTimeout the readTimeout to set
+     */
+    public void setReadTimeout( int readTimeout )
+    {
+        this.readTimeout = readTimeout;
+    }
+
+    /**
+     * @return the readTimeout
+     */
+    public int getReadTimeout()
+    {
+        return readTimeout;
+    }
+
+    /**
+     * @param openTimeout the openTimeout to set
+     */
+    public void setOpenTimeout( int openTimeout )
+    {
+        this.openTimeout = openTimeout;
+    }
+
+    /**
+     * @return the openTimeout
+     */
+    public int getOpenTimeout()
+    {
+        return openTimeout;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/server/behavior/IRemoteCacheServer.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/server/behavior/IRemoteCacheServer.java
new file mode 100644
index 0000000..ef06bc0
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/server/behavior/IRemoteCacheServer.java
@@ -0,0 +1,38 @@
+package org.apache.commons.jcs3.auxiliary.remote.server.behavior;
+
+import java.rmi.Remote;
+
+import org.apache.commons.jcs3.engine.behavior.ICacheObserver;
+import org.apache.commons.jcs3.engine.behavior.ICacheServiceAdmin;
+import org.apache.commons.jcs3.engine.behavior.ICacheServiceNonLocal;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Interface for managing Remote objects
+ *
+ * @author Thomas Vandahl
+ *
+ */
+public interface IRemoteCacheServer<K, V>
+    extends ICacheServiceNonLocal<K, V>, ICacheObserver, ICacheServiceAdmin, Remote
+{
+    // empty
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/server/behavior/IRemoteCacheServerAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/server/behavior/IRemoteCacheServerAttributes.java
new file mode 100644
index 0000000..830be83
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/server/behavior/IRemoteCacheServerAttributes.java
@@ -0,0 +1,122 @@
+package org.apache.commons.jcs3.auxiliary.remote.server.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.auxiliary.remote.behavior.ICommonRemoteCacheAttributes;
+
+/**
+ * This defines the minimal behavior for the objects that are used to configure
+ * the remote cache server.
+ */
+public interface IRemoteCacheServerAttributes
+    extends ICommonRemoteCacheAttributes
+{
+    /**
+     * Gets the localPort attribute of the IRemoteCacheAttributes object.
+     * <p>
+     * @return The localPort value
+     */
+    int getServicePort();
+
+    /**
+     * Sets the localPort attribute of the IRemoteCacheAttributes object.
+     * <p>
+     * @param p
+     *            The new localPort value
+     */
+    void setServicePort( int p );
+
+    /**
+     * Should we try to get remotely when the request does not come in from a
+     * cluster. If local L1 asks remote server R1 for element A and R1 doesn't
+     * have it, should R1 look remotely? The difference is between a local and a
+     * remote update. The local update stays local. Normal updates, removes,
+     * etc, stay local when they come from a client. If this is set to true,
+     * then they can go remote.
+     * <p>
+     * @return The localClusterConsistency value
+     */
+    boolean isAllowClusterGet();
+
+    /**
+     * Should cluster updates be propagated to the locals.
+     * <p>
+     * @param r
+     *            The new localClusterConsistency value
+     */
+    void setAllowClusterGet( boolean r );
+
+    /**
+     * Gets the ConfigFileName attribute of the IRemoteCacheAttributes object.
+     * <p>
+     * @return The configuration file name
+     */
+    String getConfigFileName();
+
+    /**
+     * Sets the ConfigFileName attribute of the IRemoteCacheAttributes object.
+     * <p>
+     * @param s
+     *            The new configuration file name
+     */
+    void setConfigFileName( String s );
+
+    /**
+     * Should we try to keep the registry alive
+     * <p>
+     * @param useRegistryKeepAlive the useRegistryKeepAlive to set
+     */
+    void setUseRegistryKeepAlive( boolean useRegistryKeepAlive );
+
+    /**
+     * Should we start the registry
+     * <p>
+     * @param startRegistry the startRegistry to set
+     * @deprecated Always true, to be removed
+     */
+    @Deprecated
+    void setStartRegistry( boolean startRegistry );
+
+    /**
+     * Should we start the registry
+     * <p>
+     * @return the startRegistry
+     * @deprecated Always true, to be removed
+     */
+    @Deprecated
+    boolean isStartRegistry();
+
+    /**
+     * Should we try to keep the registry alive
+     * <p>
+     * @return the useRegistryKeepAlive
+     */
+    boolean isUseRegistryKeepAlive();
+
+    /**
+     * @param registryKeepAliveDelayMillis the registryKeepAliveDelayMillis to set
+     */
+    void setRegistryKeepAliveDelayMillis( long registryKeepAliveDelayMillis );
+
+    /**
+     * @return the registryKeepAliveDelayMillis
+     */
+    long getRegistryKeepAliveDelayMillis();
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/server/behavior/RemoteType.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/server/behavior/RemoteType.java
new file mode 100644
index 0000000..c9be16b
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/server/behavior/RemoteType.java
@@ -0,0 +1,32 @@
+package org.apache.commons.jcs3.auxiliary.remote.server.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Enum to describe the mode of the remote cache
+ */
+public enum RemoteType
+{
+    /** A remote cache is either a local cache or a cluster cache */
+    LOCAL,
+
+    /** A remote cache is either a local cache or a cluster cache */
+    CLUSTER
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/util/RemoteCacheRequestFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/util/RemoteCacheRequestFactory.java
new file mode 100644
index 0000000..1de2457
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/util/RemoteCacheRequestFactory.java
@@ -0,0 +1,201 @@
+package org.apache.commons.jcs3.auxiliary.remote.util;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Set;
+
+import org.apache.commons.jcs3.auxiliary.remote.value.RemoteCacheRequest;
+import org.apache.commons.jcs3.auxiliary.remote.value.RemoteRequestType;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * This creates request objects. You could write your own client and use the objects from this
+ * factory.
+ */
+public class RemoteCacheRequestFactory
+{
+    /** The Logger. */
+    private static final Log log = LogManager.getLog( RemoteCacheRequestFactory.class );
+
+    /**
+     * Create generic request
+     * @param cacheName cache name
+     * @param requestType type of request
+     * @param requesterId id of requester
+     * @return the request
+     */
+    private static <K, V> RemoteCacheRequest<K, V> createRequest(String cacheName, RemoteRequestType requestType, long requesterId)
+    {
+        RemoteCacheRequest<K, V> request = new RemoteCacheRequest<>();
+        request.setCacheName( cacheName );
+        request.setRequestType( requestType );
+        request.setRequesterId( requesterId );
+
+        log.debug( "Created: {0}", request );
+
+        return request;
+    }
+
+    /**
+     * Creates a get Request.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @param requesterId
+     * @return RemoteHttpCacheRequest
+     */
+    public static <K, V> RemoteCacheRequest<K, V> createGetRequest( String cacheName, K key, long requesterId )
+    {
+        RemoteCacheRequest<K, V> request = createRequest(cacheName, RemoteRequestType.GET, requesterId);
+        request.setKey( key );
+
+        return request;
+    }
+
+    /**
+     * Creates a getMatching Request.
+     * <p>
+     * @param cacheName
+     * @param pattern
+     * @param requesterId
+     * @return RemoteHttpCacheRequest
+     */
+    public static <K, V> RemoteCacheRequest<K, V> createGetMatchingRequest( String cacheName, String pattern, long requesterId )
+    {
+        RemoteCacheRequest<K, V> request = createRequest(cacheName, RemoteRequestType.GET_MATCHING, requesterId);
+        request.setPattern( pattern );
+
+        return request;
+    }
+
+    /**
+     * Creates a getMultiple Request.
+     * <p>
+     * @param cacheName
+     * @param keys
+     * @param requesterId
+     * @return RemoteHttpCacheRequest
+     */
+    public static <K, V> RemoteCacheRequest<K, V> createGetMultipleRequest( String cacheName, Set<K> keys, long requesterId )
+    {
+        RemoteCacheRequest<K, V> request = createRequest(cacheName, RemoteRequestType.GET_MULTIPLE, requesterId);
+        request.setKeySet(keys);
+
+        return request;
+    }
+
+    /**
+     * Creates a remove Request.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @param requesterId
+     * @return RemoteHttpCacheRequest
+     */
+    public static <K, V> RemoteCacheRequest<K, V> createRemoveRequest( String cacheName, K key, long requesterId )
+    {
+        RemoteCacheRequest<K, V> request = createRequest(cacheName, RemoteRequestType.REMOVE, requesterId);
+        request.setKey( key );
+
+        return request;
+    }
+
+    /**
+     * Creates a GetKeySet Request.
+     * <p>
+     * @param cacheName
+     * @param requesterId
+     * @return RemoteHttpCacheRequest
+     */
+    public static RemoteCacheRequest<String, String> createGetKeySetRequest( String cacheName, long requesterId )
+    {
+        RemoteCacheRequest<String, String> request = createRequest(cacheName, RemoteRequestType.GET_KEYSET, requesterId);
+        request.setKey( cacheName );
+
+        return request;
+    }
+
+    /**
+     * Creates a removeAll Request.
+     * <p>
+     * @param cacheName
+     * @param requesterId
+     * @return RemoteHttpCacheRequest
+     */
+    public static <K, V> RemoteCacheRequest<K, V> createRemoveAllRequest( String cacheName, long requesterId )
+    {
+        RemoteCacheRequest<K, V> request = createRequest(cacheName, RemoteRequestType.REMOVE_ALL, requesterId);
+
+        return request;
+    }
+
+    /**
+     * Creates a dispose Request.
+     * <p>
+     * @param cacheName
+     * @param requesterId
+     * @return RemoteHttpCacheRequest
+     */
+    public static <K, V> RemoteCacheRequest<K, V> createDisposeRequest( String cacheName, long requesterId )
+    {
+        RemoteCacheRequest<K, V> request = createRequest(cacheName, RemoteRequestType.DISPOSE, requesterId);
+
+        return request;
+    }
+
+    /**
+     * Creates an Update Request.
+     * <p>
+     * @param cacheElement
+     * @param requesterId
+     * @return RemoteHttpCacheRequest
+     */
+    public static <K, V> RemoteCacheRequest<K, V> createUpdateRequest( ICacheElement<K, V> cacheElement, long requesterId )
+    {
+        RemoteCacheRequest<K, V> request = createRequest(null, RemoteRequestType.UPDATE, requesterId);
+        if ( cacheElement != null )
+        {
+            request.setCacheName( cacheElement.getCacheName() );
+            request.setCacheElement( cacheElement );
+            request.setKey( cacheElement.getKey() );
+        }
+        else
+        {
+            log.error( "Can't create a proper update request for a null cache element." );
+        }
+
+        return request;
+    }
+
+    /**
+     * Creates an alive check Request.
+     * <p>
+     * @param requesterId
+     * @return RemoteHttpCacheRequest
+     */
+    public static <K, V> RemoteCacheRequest<K, V> createAliveCheckRequest( long requesterId )
+    {
+        RemoteCacheRequest<K, V> request = createRequest(null, RemoteRequestType.ALIVE_CHECK, requesterId);
+
+        return request;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/value/RemoteCacheRequest.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/value/RemoteCacheRequest.java
new file mode 100644
index 0000000..0eb378a
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/value/RemoteCacheRequest.java
@@ -0,0 +1,187 @@
+package org.apache.commons.jcs3.auxiliary.remote.value;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.Serializable;
+import java.util.Set;
+
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+
+/**
+ * The basic request wrapper. The different types of requests are differentiated by their types.
+ * <p>
+ * Rather than creating sub object types, I created on object that has values for all types of
+ * requests.
+ */
+public class RemoteCacheRequest<K, V>
+    implements Serializable
+{
+    /** Don't change. */
+    private static final long serialVersionUID = -8858447417390442569L;
+
+    /** The request type specifies the type of request: get, put, remove, . . */
+    private RemoteRequestType requestType = null;
+
+    /** Used to identify the source. Same as listener id on the client side. */
+    private long requesterId = 0;
+
+    /** The name of the region */
+    private String cacheName;
+
+    /** The key, if this request has a key. */
+    private K key;
+
+    /** The keySet, if this request has a keySet. Only getMultiple requests. */
+    private Set<K> keySet;
+
+    /** The pattern, if this request uses a pattern. Only getMatching requests. */
+    private String pattern;
+
+    /** The ICacheEleemnt, if this request contains a value. Only update requests will have this. */
+    private ICacheElement<K, V> cacheElement;
+
+    /**
+     * @param requestType the requestType to set
+     */
+    public void setRequestType( RemoteRequestType requestType )
+    {
+        this.requestType = requestType;
+    }
+
+    /**
+     * @return the requestType
+     */
+    public RemoteRequestType getRequestType()
+    {
+        return requestType;
+    }
+
+    /**
+     * @param cacheName the cacheName to set
+     */
+    public void setCacheName( String cacheName )
+    {
+        this.cacheName = cacheName;
+    }
+
+    /**
+     * @return the cacheName
+     */
+    public String getCacheName()
+    {
+        return cacheName;
+    }
+
+    /**
+     * @param key the key to set
+     */
+    public void setKey( K key )
+    {
+        this.key = key;
+    }
+
+    /**
+     * @return the key
+     */
+    public K getKey()
+    {
+        return key;
+    }
+
+    /**
+     * @param pattern the pattern to set
+     */
+    public void setPattern( String pattern )
+    {
+        this.pattern = pattern;
+    }
+
+    /**
+     * @return the pattern
+     */
+    public String getPattern()
+    {
+        return pattern;
+    }
+
+    /**
+     * @param cacheElement the cacheElement to set
+     */
+    public void setCacheElement( ICacheElement<K, V> cacheElement )
+    {
+        this.cacheElement = cacheElement;
+    }
+
+    /**
+     * @return the cacheElement
+     */
+    public ICacheElement<K, V> getCacheElement()
+    {
+        return cacheElement;
+    }
+
+    /**
+     * @param requesterId the requesterId to set
+     */
+    public void setRequesterId( long requesterId )
+    {
+        this.requesterId = requesterId;
+    }
+
+    /**
+     * @return the requesterId
+     */
+    public long getRequesterId()
+    {
+        return requesterId;
+    }
+
+    /**
+     * @param keySet the keySet to set
+     */
+    public void setKeySet( Set<K> keySet )
+    {
+        this.keySet = keySet;
+    }
+
+    /**
+     * @return the keySet
+     */
+    public Set<K> getKeySet()
+    {
+        return keySet;
+    }
+
+    /** @return string */
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        buf.append( "\nRemoteHttpCacheRequest" );
+        buf.append( "\n requesterId [" + getRequesterId() + "]" );
+        buf.append( "\n requestType [" + getRequestType() + "]" );
+        buf.append( "\n cacheName [" + getCacheName() + "]" );
+        buf.append( "\n key [" + getKey() + "]" );
+        buf.append( "\n keySet [" + getKeySet() + "]" );
+        buf.append( "\n pattern [" + getPattern() + "]" );
+        buf.append( "\n cacheElement [" + getCacheElement() + "]" );
+        return buf.toString();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/value/RemoteCacheResponse.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/value/RemoteCacheResponse.java
new file mode 100644
index 0000000..0b346c5
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/value/RemoteCacheResponse.java
@@ -0,0 +1,105 @@
+package org.apache.commons.jcs3.auxiliary.remote.value;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.Serializable;
+
+/**
+ * This is the response wrapper. The servlet wraps all different type of responses in one of these
+ * objects.
+ */
+public class RemoteCacheResponse<T>
+    implements Serializable
+{
+    /** Don't change. */
+    private static final long serialVersionUID = -8858447417390442568L;
+
+    /** Was the event processed without error */
+    private boolean success = true;
+
+    /** Simple error messaging */
+    private String errorMessage;
+
+    /**
+     * The payload. Typically a key / ICacheElement&lt;K, V&gt; map. A normal get will return a map with one
+     * record.
+     */
+    private T payload;
+
+    /**
+     * @param success the success to set
+     */
+    public void setSuccess( boolean success )
+    {
+        this.success = success;
+    }
+
+    /**
+     * @return the success
+     */
+    public boolean isSuccess()
+    {
+        return success;
+    }
+
+    /**
+     * @param errorMessage the errorMessage to set
+     */
+    public void setErrorMessage( String errorMessage )
+    {
+        this.errorMessage = errorMessage;
+    }
+
+    /**
+     * @return the errorMessage
+     */
+    public String getErrorMessage()
+    {
+        return errorMessage;
+    }
+
+    /**
+     * @param payload the payload to set
+     */
+    public void setPayload( T payload )
+    {
+        this.payload = payload;
+    }
+
+    /**
+     * @return the payload
+     */
+    public T getPayload()
+    {
+        return payload;
+    }
+
+    /** @return string */
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        buf.append( "\nRemoteHttpCacheResponse" );
+        buf.append( "\n success [" + isSuccess() + "]" );
+        buf.append( "\n payload [" + getPayload() + "]" );
+        buf.append( "\n errorMessage [" + getErrorMessage() + "]" );
+        return buf.toString();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/value/RemoteRequestType.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/value/RemoteRequestType.java
new file mode 100644
index 0000000..6a81e0d
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/remote/value/RemoteRequestType.java
@@ -0,0 +1,53 @@
+package org.apache.commons.jcs3.auxiliary.remote.value;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * The different types of requests
+ */
+public enum RemoteRequestType
+{
+    /** Alive check request type. */
+    ALIVE_CHECK,
+
+    /** Get request type. */
+    GET,
+
+    /** Get Multiple request type. */
+    GET_MULTIPLE,
+
+    /** Get Matching request type. */
+    GET_MATCHING,
+
+    /** Update request type. */
+    UPDATE,
+
+    /** Remove request type. */
+    REMOVE,
+
+    /** Remove All request type. */
+    REMOVE_ALL,
+
+    /** Get keys request type. */
+    GET_KEYSET,
+
+    /** Dispose request type. */
+    DISPOSE,
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/AbstractCacheEventQueue.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/AbstractCacheEventQueue.java
new file mode 100644
index 0000000..c0eaba3
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/AbstractCacheEventQueue.java
@@ -0,0 +1,437 @@
+package org.apache.commons.jcs3.engine;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheEventQueue;
+import org.apache.commons.jcs3.engine.behavior.ICacheListener;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * An abstract base class to the different implementations
+ */
+public abstract class AbstractCacheEventQueue<K, V>
+    implements ICacheEventQueue<K, V>
+{
+    /** The logger. */
+    private static final Log log = LogManager.getLog( AbstractCacheEventQueue.class );
+
+    /** default */
+    protected static final int DEFAULT_WAIT_TO_DIE_MILLIS = 10000;
+
+    /**
+     * time to wait for an event before snuffing the background thread if the queue is empty. make
+     * configurable later
+     */
+    private int waitToDieMillis = DEFAULT_WAIT_TO_DIE_MILLIS;
+
+    /**
+     * When the events are pulled off the queue, then tell the listener to handle the specific event
+     * type. The work is done by the listener.
+     */
+    private ICacheListener<K, V> listener;
+
+    /** Id of the listener registered with this queue */
+    private long listenerId;
+
+    /** The cache region name, if applicable. */
+    private String cacheName;
+
+    /** Maximum number of failures before we buy the farm. */
+    private int maxFailure;
+
+    /** in milliseconds */
+    private int waitBeforeRetry;
+
+    /**
+     * This means that the queue is functional. If we reached the max number of failures, the queue
+     * is marked as non functional and will never work again.
+     */
+    private final AtomicBoolean working = new AtomicBoolean(true);
+
+    /**
+     * Returns the time to wait for events before killing the background thread.
+     * <p>
+     * @return int
+     */
+    public int getWaitToDieMillis()
+    {
+        return waitToDieMillis;
+    }
+
+    /**
+     * Sets the time to wait for events before killing the background thread.
+     * <p>
+     * @param wtdm the ms for the q to sit idle.
+     */
+    public void setWaitToDieMillis( int wtdm )
+    {
+        waitToDieMillis = wtdm;
+    }
+
+    /**
+     * Creates a brief string identifying the listener and the region.
+     * <p>
+     * @return String debugging info.
+     */
+    @Override
+    public String toString()
+    {
+        return "CacheEventQueue [listenerId=" + listenerId + ", cacheName=" + cacheName + "]";
+    }
+
+    /**
+     * @return The listenerId value
+     */
+    @Override
+    public long getListenerId()
+    {
+        return listenerId;
+    }
+
+    /**
+     * @return the cacheName
+     */
+    protected String getCacheName()
+    {
+        return cacheName;
+    }
+
+    /**
+     * Initializes the queue.
+     * <p>
+     * @param listener
+     * @param listenerId
+     * @param cacheName
+     * @param maxFailure
+     * @param waitBeforeRetry
+     */
+    protected void initialize( ICacheListener<K, V> listener, long listenerId, String cacheName, int maxFailure,
+                            int waitBeforeRetry)
+    {
+        if ( listener == null )
+        {
+            throw new IllegalArgumentException( "listener must not be null" );
+        }
+
+        this.listener = listener;
+        this.listenerId = listenerId;
+        this.cacheName = cacheName;
+        this.maxFailure = maxFailure <= 0 ? 3 : maxFailure;
+        this.waitBeforeRetry = waitBeforeRetry <= 0 ? 500 : waitBeforeRetry;
+
+        log.debug( "Constructed: {0}", this );
+    }
+
+    /**
+     * This adds a put event to the queue. When it is processed, the element will be put to the
+     * listener.
+     * <p>
+     * @param ce The feature to be added to the PutEvent attribute
+     * @throws IOException
+     */
+    @Override
+    public void addPutEvent( ICacheElement<K, V> ce )
+    {
+        put( new PutEvent( ce ) );
+    }
+
+    /**
+     * This adds a remove event to the queue. When processed the listener's remove method will be
+     * called for the key.
+     * <p>
+     * @param key The feature to be added to the RemoveEvent attribute
+     * @throws IOException
+     */
+    @Override
+    public void addRemoveEvent( K key )
+    {
+        put( new RemoveEvent( key ) );
+    }
+
+    /**
+     * This adds a remove all event to the queue. When it is processed, all elements will be removed
+     * from the cache.
+     */
+    @Override
+    public void addRemoveAllEvent()
+    {
+        put( new RemoveAllEvent() );
+    }
+
+    /**
+     * This adds a dispose event to the queue. When it is processed, the cache is shut down
+     */
+    @Override
+    public void addDisposeEvent()
+    {
+        put( new DisposeEvent() );
+    }
+
+    /**
+     * Adds an event to the queue.
+     * <p>
+     * @param event
+     */
+    protected abstract void put( AbstractCacheEvent event );
+
+
+    // /////////////////////////// Inner classes /////////////////////////////
+    /**
+     * Retries before declaring failure.
+     * <p>
+     * @author asmuts
+     */
+    protected abstract class AbstractCacheEvent implements Runnable
+    {
+        /** Number of failures encountered processing this event. */
+        int failures = 0;
+
+        /**
+         * Main processing method for the AbstractCacheEvent object
+         */
+        @Override
+        @SuppressWarnings("synthetic-access")
+        public void run()
+        {
+            try
+            {
+                doRun();
+            }
+            catch ( IOException e )
+            {
+                log.warn( e );
+                if ( ++failures >= maxFailure )
+                {
+                    log.warn( "Error while running event from Queue: {0}. "
+                            + "Dropping Event and marking Event Queue as "
+                            + "non-functional.", this );
+                    destroy();
+                    return;
+                }
+                log.info( "Error while running event from Queue: {0}. "
+                        + "Retrying...", this );
+                try
+                {
+                    Thread.sleep( waitBeforeRetry );
+                    run();
+                }
+                catch ( InterruptedException ie )
+                {
+                    log.warn( "Interrupted while sleeping for retry on event "
+                            + "{0}.", this );
+                    destroy();
+                }
+            }
+        }
+
+        /**
+         * @throws IOException
+         */
+        protected abstract void doRun()
+            throws IOException;
+    }
+
+    /**
+     * An element should be put in the cache.
+     * <p>
+     * @author asmuts
+     */
+    protected class PutEvent
+        extends AbstractCacheEvent
+    {
+        /** The element to put to the listener */
+        private final ICacheElement<K, V> ice;
+
+        /**
+         * Constructor for the PutEvent object.
+         * <p>
+         * @param ice
+         */
+        PutEvent( ICacheElement<K, V> ice )
+        {
+            this.ice = ice;
+        }
+
+        /**
+         * Call put on the listener.
+         * <p>
+         * @throws IOException
+         */
+        @Override
+        protected void doRun()
+            throws IOException
+        {
+            listener.handlePut( ice );
+        }
+
+        /**
+         * For debugging.
+         * <p>
+         * @return Info on the key and value.
+         */
+        @Override
+        public String toString()
+        {
+            return new StringBuilder( "PutEvent for key: " )
+                    .append( ice.getKey() )
+                    .append( " value: " )
+                    .append( ice.getVal() )
+                    .toString();
+        }
+
+    }
+
+    /**
+     * An element should be removed from the cache.
+     * <p>
+     * @author asmuts
+     */
+    protected class RemoveEvent
+        extends AbstractCacheEvent
+    {
+        /** The key to remove from the listener */
+        private final K key;
+
+        /**
+         * Constructor for the RemoveEvent object
+         * <p>
+         * @param key
+         */
+        RemoveEvent( K key )
+        {
+            this.key = key;
+        }
+
+        /**
+         * Call remove on the listener.
+         * <p>
+         * @throws IOException
+         */
+        @Override
+        protected void doRun()
+            throws IOException
+        {
+            listener.handleRemove( cacheName, key );
+        }
+
+        /**
+         * For debugging.
+         * <p>
+         * @return Info on the key to remove.
+         */
+        @Override
+        public String toString()
+        {
+            return new StringBuilder( "RemoveEvent for " )
+                    .append( key )
+                    .toString();
+        }
+
+    }
+
+    /**
+     * All elements should be removed from the cache when this event is processed.
+     * <p>
+     * @author asmuts
+     */
+    protected class RemoveAllEvent
+        extends AbstractCacheEvent
+    {
+        /**
+         * Call removeAll on the listener.
+         * <p>
+         * @throws IOException
+         */
+        @Override
+        protected void doRun()
+            throws IOException
+        {
+            listener.handleRemoveAll( cacheName );
+        }
+
+        /**
+         * For debugging.
+         * <p>
+         * @return The name of the event.
+         */
+        @Override
+        public String toString()
+        {
+            return "RemoveAllEvent";
+        }
+    }
+
+    /**
+     * The cache should be disposed when this event is processed.
+     * <p>
+     * @author asmuts
+     */
+    protected class DisposeEvent
+        extends AbstractCacheEvent
+    {
+        /**
+         * Called when gets to the end of the queue
+         * <p>
+         * @throws IOException
+         */
+        @Override
+        protected void doRun()
+            throws IOException
+        {
+            listener.handleDispose( cacheName );
+        }
+
+        /**
+         * For debugging.
+         * <p>
+         * @return The name of the event.
+         */
+        @Override
+        public String toString()
+        {
+            return "DisposeEvent";
+        }
+    }
+
+    /**
+     * @return whether the queue is functional.
+     */
+    @Override
+    public boolean isWorking()
+    {
+        return working.get();
+    }
+
+    /**
+     * This means that the queue is functional. If we reached the max number of failures, the queue
+     * is marked as non functional and will never work again.
+     * <p>
+     * @param b
+     */
+    public void setWorking( boolean b )
+    {
+        working.set(b);
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CacheAdaptor.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CacheAdaptor.java
new file mode 100644
index 0000000..60d5604
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CacheAdaptor.java
@@ -0,0 +1,137 @@
+package org.apache.commons.jcs3.engine;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+
+import org.apache.commons.jcs3.engine.behavior.ICache;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheListener;
+
+/**
+ * Used for Cache-to-Cache messaging purposes. These are used in the balking
+ * facades in the lateral and remote caches.
+ */
+public class CacheAdaptor<K, V>
+    implements ICacheListener<K, V>
+{
+    /** The cache we are adapting. */
+    private final ICache<K, V> cache;
+
+    /** The unique id of this listener. */
+    private long listenerId = 0;
+
+    /**
+     * Sets the listenerId attribute of the CacheAdaptor object
+     * <p>
+     * @param id
+     *            The new listenerId value
+     * @throws IOException
+     */
+    @Override
+    public void setListenerId( long id )
+        throws IOException
+    {
+        this.listenerId = id;
+    }
+
+    /**
+     * Gets the listenerId attribute of the CacheAdaptor object
+     * <p>
+     * @return The listenerId value
+     * @throws IOException
+     */
+    @Override
+    public long getListenerId()
+        throws IOException
+    {
+        return this.listenerId;
+    }
+
+    /**
+     * Constructor for the CacheAdaptor object
+     * <p>
+     * @param cache
+     */
+    public CacheAdaptor( ICache<K, V> cache )
+    {
+        this.cache = cache;
+    }
+
+    /**
+     * Puts an item into the cache.
+     * <p>
+     * @param item
+     * @throws IOException
+     */
+    @Override
+    public void handlePut( ICacheElement<K, V> item )
+        throws IOException
+    {
+        try
+        {
+            cache.update( item );
+        }
+        catch ( IOException e )
+        {
+            // swallow
+        }
+    }
+
+    /**
+     * Removes an item.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @throws IOException
+     */
+    @Override
+    public void handleRemove( String cacheName, K key )
+        throws IOException
+    {
+        cache.remove( key );
+    }
+
+    /**
+     * Clears the region.
+     * <p>
+     * @param cacheName
+     * @throws IOException
+     */
+    @Override
+    public void handleRemoveAll( String cacheName )
+        throws IOException
+    {
+        cache.removeAll();
+    }
+
+    /**
+     * Shutdown call.
+     * <p>
+     * @param cacheName
+     * @throws IOException
+     */
+    @Override
+    public void handleDispose( String cacheName )
+        throws IOException
+    {
+        cache.dispose();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CacheElement.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CacheElement.java
new file mode 100644
index 0000000..9d64993
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CacheElement.java
@@ -0,0 +1,160 @@
+package org.apache.commons.jcs3.engine;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+
+/**
+ * Generic element wrapper. Often stuffed inside another.
+ */
+public class CacheElement<K, V>
+    implements ICacheElement<K, V>
+{
+    /** Don't change */
+    private static final long serialVersionUID = -6062305728297627263L;
+
+    /** The name of the cache region. This is a namespace. */
+    private final String cacheName;
+
+    /** This is the cache key by which the value can be referenced. */
+    private final K key;
+
+    /** This is the cached value, reference by the key. */
+    private final V val;
+
+    /**
+     * These attributes hold information about the element and what it is
+     * allowed to do.
+     */
+    private IElementAttributes attr;
+
+    /**
+     * Constructor for the CacheElement object
+     * <p>
+     * @param cacheName
+     * @param key
+     * @param val
+     */
+    public CacheElement( String cacheName, K key, V val )
+    {
+        this.cacheName = cacheName;
+        this.key = key;
+        this.val = val;
+    }
+
+    /**
+     * Constructor for the CacheElement object
+     * <p>
+     * @param cacheName
+     * @param key
+     * @param val
+     * @param attrArg
+     */
+    public CacheElement( String cacheName, K key, V val, IElementAttributes attrArg )
+    {
+        this(cacheName, key, val);
+        this.attr = attrArg;
+    }
+
+    /**
+     * Gets the cacheName attribute of the CacheElement object
+     * <p>
+     * @return The cacheName value
+     */
+    @Override
+    public String getCacheName()
+    {
+        return this.cacheName;
+    }
+
+    /**
+     * Gets the key attribute of the CacheElement object
+     * <p>
+     * @return The key value
+     */
+    @Override
+    public K getKey()
+    {
+        return this.key;
+    }
+
+    /**
+     * Gets the val attribute of the CacheElement object
+     * <p>
+     * @return The val value
+     */
+    @Override
+    public V getVal()
+    {
+        return this.val;
+    }
+
+    /**
+     * Sets the attributes attribute of the CacheElement object
+     * <p>
+     * @param attr
+     *            The new IElementAttributes value
+     */
+    @Override
+    public void setElementAttributes( IElementAttributes attr )
+    {
+        this.attr = attr;
+    }
+
+    /**
+     * Gets the IElementAttributes attribute of the CacheElement object
+     * <p>
+     * @return The IElementAttributes value, never null
+     */
+    @Override
+    public IElementAttributes getElementAttributes()
+    {
+        // create default attributes if they are null
+        // this shouldn't happen, but could if a corrupt
+        // object was sent over the wire.
+        if ( this.attr == null )
+        {
+            this.attr = new ElementAttributes();
+        }
+        return this.attr;
+    }
+
+    /**
+     * @return a hash of the key only
+     */
+    @Override
+    public int hashCode()
+    {
+        return key.hashCode();
+    }
+
+    /**
+     * For debugging only.
+     * <p>
+     * @return String representation
+     */
+    @Override
+    public String toString()
+    {
+        return "[CacheElement: cacheName [" + cacheName + "], key [" + key + "], val [" + val + "], attr [" + attr
+            + "]";
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CacheElementSerialized.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CacheElementSerialized.java
new file mode 100644
index 0000000..c1b0bdc
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CacheElementSerialized.java
@@ -0,0 +1,77 @@
+package org.apache.commons.jcs3.engine;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Arrays;
+
+import org.apache.commons.jcs3.engine.behavior.ICacheElementSerialized;
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+
+/** Either serialized value or the value should be null; */
+public class CacheElementSerialized<K, V>
+    extends CacheElement<K, V>
+    implements ICacheElementSerialized<K, V>
+{
+    /** Don't change. */
+    private static final long serialVersionUID = -7265084818647601874L;
+
+    /** The serialized value. */
+    private final byte[] serializedValue;
+
+    /**
+     * Constructs a usable wrapper.
+     * <p>
+     * @param cacheNameArg
+     * @param keyArg
+     * @param serializedValueArg
+     * @param elementAttributesArg
+     */
+    public CacheElementSerialized( String cacheNameArg, K keyArg, byte[] serializedValueArg,
+                                   IElementAttributes elementAttributesArg )
+    {
+        super(cacheNameArg, keyArg, null, elementAttributesArg);
+        this.serializedValue = serializedValueArg;
+    }
+
+    /** @return byte[] */
+    @Override
+    public byte[] getSerializedValue()
+    {
+        return this.serializedValue;
+    }
+
+    /**
+     * For debugging only.
+     * <p>
+     * @return debugging string.
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        buf.append( "\n CacheElementSerialized: " );
+        buf.append( "\n CacheName = [" + getCacheName() + "]" );
+        buf.append( "\n Key = [" + getKey() + "]" );
+        buf.append( "\n SerializedValue = " + Arrays.toString(getSerializedValue()) );
+        buf.append( "\n ElementAttributes = " + getElementAttributes() );
+        return buf.toString();
+    }
+
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CacheEventQueue.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CacheEventQueue.java
new file mode 100644
index 0000000..f764981
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CacheEventQueue.java
@@ -0,0 +1,95 @@
+package org.apache.commons.jcs3.engine;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.engine.behavior.ICacheListener;
+import org.apache.commons.jcs3.utils.threadpool.PoolConfiguration;
+import org.apache.commons.jcs3.utils.threadpool.ThreadPoolManager;
+import org.apache.commons.jcs3.utils.threadpool.PoolConfiguration.WhenBlockedPolicy;
+
+/**
+ * An event queue is used to propagate ordered cache events to one and only one target listener.
+ */
+public class CacheEventQueue<K, V>
+    extends PooledCacheEventQueue<K, V>
+{
+    /** The type of queue -- there are pooled and single */
+    private static final QueueType queueType = QueueType.SINGLE;
+
+    /**
+     * Constructs with the specified listener and the cache name.
+     * <p>
+     * @param listener
+     * @param listenerId
+     * @param cacheName
+     */
+    public CacheEventQueue( ICacheListener<K, V> listener, long listenerId, String cacheName )
+    {
+        this( listener, listenerId, cacheName, 10, 500 );
+    }
+
+    /**
+     * Constructor for the CacheEventQueue object
+     * <p>
+     * @param listener
+     * @param listenerId
+     * @param cacheName
+     * @param maxFailure
+     * @param waitBeforeRetry
+     */
+    public CacheEventQueue( ICacheListener<K, V> listener, long listenerId, String cacheName, int maxFailure,
+                            int waitBeforeRetry )
+    {
+        super( listener, listenerId, cacheName, maxFailure, waitBeforeRetry, null );
+    }
+
+    /**
+     * Initializes the queue.
+     * <p>
+     * @param listener
+     * @param listenerId
+     * @param cacheName
+     * @param maxFailure
+     * @param waitBeforeRetry
+     * @param threadPoolName
+     */
+    @Override
+    protected void initialize( ICacheListener<K, V> listener, long listenerId, String cacheName, int maxFailure,
+                            int waitBeforeRetry, String threadPoolName )
+    {
+        super.initialize(listener, listenerId, cacheName, maxFailure, waitBeforeRetry);
+
+        // create a default pool with one worker thread to mimic the SINGLE queue behavior
+        pool = ThreadPoolManager.getInstance().createPool(
+        		new PoolConfiguration(false, 0, 1, 0, getWaitToDieMillis(), WhenBlockedPolicy.RUN, 0),
+        		"CacheEventQueue.QProcessor-" + getCacheName());
+    }
+
+    /**
+     * What type of queue is this.
+     * <p>
+     * @return queueType
+     */
+    @Override
+    public QueueType getQueueType()
+    {
+        return queueType;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CacheEventQueueFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CacheEventQueueFactory.java
new file mode 100644
index 0000000..a89a060
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CacheEventQueueFactory.java
@@ -0,0 +1,85 @@
+package org.apache.commons.jcs3.engine;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.engine.behavior.ICacheEventQueue;
+import org.apache.commons.jcs3.engine.behavior.ICacheListener;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * This class hands out event Queues. This allows us to change the implementation more easily. You
+ * can confugure the cache to use a custom type.
+ * <p>
+ * @author aaronsm
+ */
+public class CacheEventQueueFactory<K, V>
+{
+    /** The logger. */
+    private static final Log log = LogManager.getLog( CacheEventQueueFactory.class );
+
+    /**
+     * The most commonly used factory method.
+     * <p>
+     * @param listener
+     * @param listenerId
+     * @param cacheName
+     * @param threadPoolName
+     * @param poolType - SINGLE, POOLED
+     * @return ICacheEventQueue
+     */
+    public ICacheEventQueue<K, V> createCacheEventQueue( ICacheListener<K, V> listener, long listenerId, String cacheName,
+                                                   String threadPoolName, ICacheEventQueue.QueueType poolType )
+    {
+        return createCacheEventQueue( listener, listenerId, cacheName, 10, 500, threadPoolName, poolType );
+    }
+
+    /**
+     * Fully configured event queue.
+     * <p>
+     * @param listener
+     * @param listenerId
+     * @param cacheName
+     * @param maxFailure
+     * @param waitBeforeRetry
+     * @param threadPoolName null is OK, if not a pooled event queue this is ignored
+     * @param poolType single or pooled
+     * @return ICacheEventQueue
+     */
+    public ICacheEventQueue<K, V> createCacheEventQueue( ICacheListener<K, V> listener, long listenerId, String cacheName,
+                                                   int maxFailure, int waitBeforeRetry, String threadPoolName,
+                                                   ICacheEventQueue.QueueType poolType )
+    {
+        log.debug( "threadPoolName = [{0}] poolType = {1}", threadPoolName, poolType );
+
+        ICacheEventQueue<K, V> eventQueue = null;
+        if ( poolType == null || ICacheEventQueue.QueueType.SINGLE == poolType )
+        {
+            eventQueue = new CacheEventQueue<>( listener, listenerId, cacheName, maxFailure, waitBeforeRetry );
+        }
+        else if ( ICacheEventQueue.QueueType.POOLED == poolType )
+        {
+            eventQueue = new PooledCacheEventQueue<>( listener, listenerId, cacheName, maxFailure, waitBeforeRetry,
+                                                    threadPoolName );
+        }
+
+        return eventQueue;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CacheGroup.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CacheGroup.java
new file mode 100644
index 0000000..179aae7
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CacheGroup.java
@@ -0,0 +1,59 @@
+package org.apache.commons.jcs3.engine;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+
+/**
+ * Holder for attributes specific to a group. The grouping functionality is on
+ * the way out.
+ */
+public class CacheGroup
+{
+    /** Element configuration. */
+    private IElementAttributes attr;
+
+    /** Constructor for the CacheGroup object */
+    public CacheGroup()
+    {
+        super();
+    }
+
+    /**
+     * Sets the attributes attribute of the CacheGroup object
+     * <p>
+     * @param attr
+     *            The new attributes value
+     */
+    public void setElementAttributes( IElementAttributes attr )
+    {
+        this.attr = attr;
+    }
+
+    /**
+     * Gets the attrributes attribute of the CacheGroup object
+     * <p>
+     * @return The attrributes value
+     */
+    public IElementAttributes getElementAttrributes()
+    {
+        return attr;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CacheInfo.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CacheInfo.java
new file mode 100644
index 0000000..a303f6e
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CacheInfo.java
@@ -0,0 +1,47 @@
+package org.apache.commons.jcs3.engine;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.rmi.dgc.VMID;
+
+/**
+ * This is a static variable holder for the distribution auxiliaries that need something like a vmid.
+ */
+public final class CacheInfo
+{
+    /** shouldn't be instantiated */
+    private CacheInfo()
+    {
+        super();
+    }
+
+    /**
+     * Used to identify a client, so we can run multiple clients off one host.
+     * Need since there is no way to identify a client other than by host in
+     * rmi.
+     * <p>
+     * TODO: may have some trouble in failover mode if the cache keeps its old
+     * id. We may need to reset this when moving into failover.
+     */
+    private static final VMID vmid = new VMID();
+
+    /** By default this is the hashcode of the VMID */
+    public static final long listenerId = vmid.hashCode();
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CacheListeners.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CacheListeners.java
new file mode 100644
index 0000000..14a5b98
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CacheListeners.java
@@ -0,0 +1,79 @@
+package org.apache.commons.jcs3.engine;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.commons.jcs3.engine.behavior.ICache;
+import org.apache.commons.jcs3.engine.behavior.ICacheEventQueue;
+
+/**
+ * Used to associates a set of [cache listener to cache event queue] for a
+ * cache.
+ */
+public class CacheListeners<K, V>
+{
+    /** The cache using the queue. */
+    public final ICache<K, V> cache;
+
+    /** Map ICacheListener to ICacheEventQueue */
+    public final ConcurrentMap<Long, ICacheEventQueue<K, V>> eventQMap =
+        new ConcurrentHashMap<>();
+
+    /**
+     * Constructs with the given cache.
+     * <p>
+     * @param cache
+     */
+    public CacheListeners( ICache<K, V> cache )
+    {
+        if ( cache == null )
+        {
+            throw new IllegalArgumentException( "cache must not be null" );
+        }
+        this.cache = cache;
+    }
+
+    /** @return info on the listeners */
+    @Override
+    public String toString()
+    {
+        StringBuilder buffer = new StringBuilder();
+        buffer.append( "\n CacheListeners" );
+        if ( cache != null )
+        {
+            buffer.append( "\n Region = " + cache.getCacheName() );
+        }
+        if ( eventQMap != null )
+        {
+            buffer.append( "\n Event Queue Map " );
+            buffer.append( "\n size = " + eventQMap.size() );
+            eventQMap.forEach((key, value)
+                    -> buffer.append( "\n Entry: key: ").append(key)
+                        .append(", value: ").append(value));
+        }
+        else
+        {
+            buffer.append( "\n No Listeners. " );
+        }
+        return buffer.toString();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CacheStatus.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CacheStatus.java
new file mode 100644
index 0000000..99bfc7f
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CacheStatus.java
@@ -0,0 +1,37 @@
+package org.apache.commons.jcs3.engine;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Cache statuses
+ * <p>
+ * @version $Id: CacheStatus.java 536904 2007-05-10 16:03:42Z tv $
+ */
+public enum CacheStatus
+{
+    /** Cache alive status. */
+    ALIVE,
+
+    /** Cache disposed status. */
+    DISPOSED,
+
+    /** Cache in error. */
+    ERROR
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CacheWatchRepairable.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CacheWatchRepairable.java
new file mode 100644
index 0000000..342479b
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CacheWatchRepairable.java
@@ -0,0 +1,170 @@
+package org.apache.commons.jcs3.engine;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import org.apache.commons.jcs3.engine.behavior.ICacheListener;
+import org.apache.commons.jcs3.engine.behavior.ICacheObserver;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * Intercepts the requests to the underlying ICacheObserver object so that the listeners can be
+ * recorded locally for remote connection recovery purposes. (Durable subscription like those in JMS
+ * is not implemented at this stage for it can be too expensive.)
+ */
+public class CacheWatchRepairable
+    implements ICacheObserver
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog( CacheWatchRepairable.class );
+
+    /** the underlying ICacheObserver. */
+    private ICacheObserver cacheWatch;
+
+    /** Map of cache regions. */
+    private final ConcurrentMap<String, Set<ICacheListener<?, ?>>> cacheMap =
+        new ConcurrentHashMap<>();
+
+    /**
+     * Replaces the underlying cache watch service and re-attaches all existing listeners to the new
+     * cache watch.
+     * <p>
+     * @param cacheWatch The new cacheWatch value
+     */
+    public void setCacheWatch( ICacheObserver cacheWatch )
+    {
+        this.cacheWatch = cacheWatch;
+        for (Map.Entry<String, Set<ICacheListener<?, ?>>> entry : cacheMap.entrySet())
+        {
+            String cacheName = entry.getKey();
+            for (ICacheListener<?, ?> listener : entry.getValue())
+            {
+                try
+                {
+                    log.info( "Adding listener to cache watch. ICacheListener = "
+                            + "{0} | ICacheObserver = {1}", listener, cacheWatch );
+                    cacheWatch.addCacheListener( cacheName, listener );
+                }
+                catch ( IOException ex )
+                {
+                    log.error( "Problem adding listener. ICacheListener = {0} | "
+                            + "ICacheObserver = {1}", listener, cacheWatch, ex );
+                }
+            }
+        }
+    }
+
+    /**
+     * Adds a feature to the CacheListener attribute of the CacheWatchRepairable object
+     * <p>
+     * @param cacheName The feature to be added to the CacheListener attribute
+     * @param obj The feature to be added to the CacheListener attribute
+     * @throws IOException
+     */
+    @Override
+    public <K, V> void addCacheListener( String cacheName, ICacheListener<K, V> obj )
+        throws IOException
+    {
+        // Record the added cache listener locally, regardless of whether the
+        // remote add-listener operation succeeds or fails.
+        Set<ICacheListener<?, ?>> listenerSet = cacheMap.computeIfAbsent(cacheName, key -> {
+            return new CopyOnWriteArraySet<>();
+        });
+
+        listenerSet.add( obj );
+
+        log.info( "Adding listener to cache watch. ICacheListener = {0} | "
+                + "ICacheObserver = {1} | cacheName = {2}", obj, cacheWatch,
+                cacheName );
+        cacheWatch.addCacheListener( cacheName, obj );
+    }
+
+    /**
+     * Adds a feature to the CacheListener attribute of the CacheWatchRepairable object
+     * <p>
+     * @param obj The feature to be added to the CacheListener attribute
+     * @throws IOException
+     */
+    @Override
+    public <K, V> void addCacheListener( ICacheListener<K, V> obj )
+        throws IOException
+    {
+        // Record the added cache listener locally, regardless of whether the
+        // remote add-listener operation succeeds or fails.
+        for (Set<ICacheListener<?, ?>> listenerSet : cacheMap.values())
+        {
+            listenerSet.add( obj );
+        }
+
+        log.info( "Adding listener to cache watch. ICacheListener = {0} | "
+                + "ICacheObserver = {1}", obj, cacheWatch );
+        cacheWatch.addCacheListener( obj );
+    }
+
+    /**
+     * Tell the server to release us.
+     * <p>
+     * @param cacheName
+     * @param obj
+     * @throws IOException
+     */
+    @Override
+    public <K, V> void removeCacheListener( String cacheName, ICacheListener<K, V> obj )
+        throws IOException
+    {
+        log.info( "removeCacheListener, cacheName [{0}]", cacheName );
+        // Record the removal locally, regardless of whether the remote
+        // remove-listener operation succeeds or fails.
+        Set<ICacheListener<?, ?>> listenerSet = cacheMap.get( cacheName );
+        if ( listenerSet != null )
+        {
+            listenerSet.remove( obj );
+        }
+        cacheWatch.removeCacheListener( cacheName, obj );
+    }
+
+    /**
+     * @param obj
+     * @throws IOException
+     */
+    @Override
+    public <K, V> void removeCacheListener( ICacheListener<K, V> obj )
+        throws IOException
+    {
+        log.info( "removeCacheListener, ICacheListener [{0}]", obj );
+
+        // Record the removal locally, regardless of whether the remote
+        // remove-listener operation succeeds or fails.
+        for (Set<ICacheListener<?, ?>> listenerSet : cacheMap.values())
+        {
+            log.debug( "Before removing [{0}] the listenerSet = {1}", obj,
+                    listenerSet );
+            listenerSet.remove( obj );
+        }
+        cacheWatch.removeCacheListener( obj );
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CompositeCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CompositeCacheAttributes.java
new file mode 100644
index 0000000..ea9241f
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/CompositeCacheAttributes.java
@@ -0,0 +1,443 @@
+package org.apache.commons.jcs3.engine;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes;
+
+/**
+ * The CompositeCacheAttributes defines the general cache region settings. If a region is not
+ * explicitly defined in the cache.ccf then it inherits the cache default settings.
+ * <p>
+ * If all the default attributes are not defined in the default region definition in the cache.ccf,
+ * the hard coded defaults will be used.
+ */
+public class CompositeCacheAttributes
+    implements ICompositeCacheAttributes
+{
+    /** Don't change */
+    private static final long serialVersionUID = 6754049978134196787L;
+
+    /** default lateral switch */
+    private static final boolean DEFAULT_USE_LATERAL = true;
+
+    /** default remote switch */
+    private static final boolean DEFAULT_USE_REMOTE = true;
+
+    /** default disk switch */
+    private static final boolean DEFAULT_USE_DISK = true;
+
+    /** default shrinker setting */
+    private static final boolean DEFAULT_USE_SHRINKER = false;
+
+    /** default max objects value */
+    private static final int DEFAULT_MAX_OBJECTS = 100;
+
+    /** default */
+    private static final int DEFAULT_MAX_MEMORY_IDLE_TIME_SECONDS = 60 * 120;
+
+    /** default interval to run the shrinker */
+    private static final int DEFAULT_SHRINKER_INTERVAL_SECONDS = 30;
+
+    /** default */
+    private static final int DEFAULT_MAX_SPOOL_PER_RUN = -1;
+
+    /** default */
+    private static final String DEFAULT_MEMORY_CACHE_NAME = "org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache";
+
+    /** Default number to send to disk at a time when memory fills. */
+    private static final int DEFAULT_CHUNK_SIZE = 2;
+
+    /** allow lateral caches */
+    private boolean useLateral = DEFAULT_USE_LATERAL;
+
+    /** allow remote caches */
+    private boolean useRemote = DEFAULT_USE_REMOTE;
+
+    /** Whether we should use a disk cache if it is configured. */
+    private boolean useDisk = DEFAULT_USE_DISK;
+
+    /** Whether or not we should run the memory shrinker thread. */
+    private boolean useMemoryShrinker = DEFAULT_USE_SHRINKER;
+
+    /** The maximum objects that the memory cache will be allowed to hold. */
+    private int maxObjs = DEFAULT_MAX_OBJECTS;
+
+    /** maxMemoryIdleTimeSeconds */
+    private long maxMemoryIdleTimeSeconds = DEFAULT_MAX_MEMORY_IDLE_TIME_SECONDS;
+
+    /** shrinkerIntervalSeconds */
+    private long shrinkerIntervalSeconds = DEFAULT_SHRINKER_INTERVAL_SECONDS;
+
+    /** The maximum number the shrinker will spool to disk per run. */
+    private int maxSpoolPerRun = DEFAULT_MAX_SPOOL_PER_RUN;
+
+    /** The name of this cache region. */
+    private String cacheName;
+
+    /** The name of the memory cache implementation class. */
+    private String memoryCacheName;
+
+    /** Set via DISK_USAGE_PATTERN_NAME */
+    private DiskUsagePattern diskUsagePattern = DiskUsagePattern.SWAP;
+
+    /** How many to spool to disk at a time. */
+    private int spoolChunkSize = DEFAULT_CHUNK_SIZE;
+
+    /**
+     * Constructor for the CompositeCacheAttributes object
+     */
+    public CompositeCacheAttributes()
+    {
+        super();
+        // set this as the default so the configuration is a bit simpler
+        memoryCacheName = DEFAULT_MEMORY_CACHE_NAME;
+    }
+
+    /**
+     * Sets the maxObjects attribute of the CompositeCacheAttributes object
+     * <p>
+     * @param maxObjs The new maxObjects value
+     */
+    @Override
+    public void setMaxObjects( int maxObjs )
+    {
+        this.maxObjs = maxObjs;
+    }
+
+    /**
+     * Gets the maxObjects attribute of the CompositeCacheAttributes object
+     * <p>
+     * @return The maxObjects value
+     */
+    @Override
+    public int getMaxObjects()
+    {
+        return this.maxObjs;
+    }
+
+    /**
+     * Sets the useDisk attribute of the CompositeCacheAttributes object
+     * <p>
+     * @param useDisk The new useDisk value
+     */
+    @Override
+    public void setUseDisk( boolean useDisk )
+    {
+        this.useDisk = useDisk;
+    }
+
+    /**
+     * Gets the useDisk attribute of the CompositeCacheAttributes object
+     * <p>
+     * @return The useDisk value
+     */
+    @Override
+    public boolean isUseDisk()
+    {
+        return useDisk;
+    }
+
+    /**
+     * Sets the useLateral attribute of the CompositeCacheAttributes object
+     * <p>
+     * @param b The new useLateral value
+     */
+    @Override
+    public void setUseLateral( boolean b )
+    {
+        this.useLateral = b;
+    }
+
+    /**
+     * Gets the useLateral attribute of the CompositeCacheAttributes object
+     * <p>
+     * @return The useLateral value
+     */
+    @Override
+    public boolean isUseLateral()
+    {
+        return this.useLateral;
+    }
+
+    /**
+     * Sets the useRemote attribute of the CompositeCacheAttributes object
+     * <p>
+     * @param useRemote The new useRemote value
+     */
+    @Override
+    public void setUseRemote( boolean useRemote )
+    {
+        this.useRemote = useRemote;
+    }
+
+    /**
+     * Gets the useRemote attribute of the CompositeCacheAttributes object
+     * <p>
+     * @return The useRemote value
+     */
+    @Override
+    public boolean isUseRemote()
+    {
+        return this.useRemote;
+    }
+
+    /**
+     * Sets the cacheName attribute of the CompositeCacheAttributes object
+     * <p>
+     * @param s The new cacheName value
+     */
+    @Override
+    public void setCacheName( String s )
+    {
+        this.cacheName = s;
+    }
+
+    /**
+     * Gets the cacheName attribute of the CompositeCacheAttributes object
+     * <p>
+     * @return The cacheName value
+     */
+    @Override
+    public String getCacheName()
+    {
+        return this.cacheName;
+    }
+
+    /**
+     * Sets the memoryCacheName attribute of the CompositeCacheAttributes object
+     * <p>
+     * @param s The new memoryCacheName value
+     */
+    @Override
+    public void setMemoryCacheName( String s )
+    {
+        this.memoryCacheName = s;
+    }
+
+    /**
+     * Gets the memoryCacheName attribute of the CompositeCacheAttributes object
+     * <p>
+     * @return The memoryCacheName value
+     */
+    @Override
+    public String getMemoryCacheName()
+    {
+        return this.memoryCacheName;
+    }
+
+    /**
+     * Whether the memory cache should perform background memory shrinkage.
+     * <p>
+     * @param useShrinker The new UseMemoryShrinker value
+     */
+    @Override
+    public void setUseMemoryShrinker( boolean useShrinker )
+    {
+        this.useMemoryShrinker = useShrinker;
+    }
+
+    /**
+     * Whether the memory cache should perform background memory shrinkage.
+     * <p>
+     * @return The UseMemoryShrinker value
+     */
+    @Override
+    public boolean isUseMemoryShrinker()
+    {
+        return this.useMemoryShrinker;
+    }
+
+    /**
+     * If UseMemoryShrinker is true the memory cache should auto-expire elements to reclaim space.
+     * <p>
+     * @param seconds The new MaxMemoryIdleTimeSeconds value
+     */
+    @Override
+    public void setMaxMemoryIdleTimeSeconds( long seconds )
+    {
+        this.maxMemoryIdleTimeSeconds = seconds;
+    }
+
+    /**
+     * If UseMemoryShrinker is true the memory cache should auto-expire elements to reclaim space.
+     * <p>
+     * @return The MaxMemoryIdleTimeSeconds value
+     */
+    @Override
+    public long getMaxMemoryIdleTimeSeconds()
+    {
+        return this.maxMemoryIdleTimeSeconds;
+    }
+
+    /**
+     * If UseMemoryShrinker is true the memory cache should auto-expire elements to reclaim space.
+     * This sets the shrinker interval.
+     * <p>
+     * @param seconds The new ShrinkerIntervalSeconds value
+     */
+    @Override
+    public void setShrinkerIntervalSeconds( long seconds )
+    {
+        this.shrinkerIntervalSeconds = seconds;
+    }
+
+    /**
+     * If UseMemoryShrinker is true the memory cache should auto-expire elements to reclaim space.
+     * This gets the shrinker interval.
+     * <p>
+     * @return The ShrinkerIntervalSeconds value
+     */
+    @Override
+    public long getShrinkerIntervalSeconds()
+    {
+        return this.shrinkerIntervalSeconds;
+    }
+
+    /**
+     * If UseMemoryShrinker is true the memory cache should auto-expire elements to reclaim space.
+     * This sets the maximum number of items to spool per run.
+     * <p>
+     * If the value is -1, then there is no limit to the number of items to be spooled.
+     * <p>
+     * @param maxSpoolPerRun The new maxSpoolPerRun value
+     */
+    @Override
+    public void setMaxSpoolPerRun( int maxSpoolPerRun )
+    {
+        this.maxSpoolPerRun = maxSpoolPerRun;
+    }
+
+    /**
+     * If UseMemoryShrinker is true the memory cache should auto-expire elements to reclaim space.
+     * This gets the maximum number of items to spool per run.
+     * <p>
+     * @return The maxSpoolPerRun value
+     */
+    @Override
+    public int getMaxSpoolPerRun()
+    {
+        return this.maxSpoolPerRun;
+    }
+
+    /**
+     * By default this is SWAP_ONLY.
+     * <p>
+     * @param diskUsagePattern The diskUsagePattern to set.
+     */
+    @Override
+    public void setDiskUsagePattern( DiskUsagePattern diskUsagePattern )
+    {
+        this.diskUsagePattern = diskUsagePattern;
+    }
+
+    /**
+     * Translates the name to the disk usage pattern short value.
+     * <p>
+     * The allowed values are SWAP and UPDATE.
+     * <p>
+     * @param diskUsagePatternName The diskUsagePattern to set.
+     */
+    @Override
+    public void setDiskUsagePatternName( String diskUsagePatternName )
+    {
+        if ( diskUsagePatternName != null )
+        {
+            String name = diskUsagePatternName.toUpperCase().trim();
+            if ( name.startsWith( "SWAP" ) )
+            {
+                this.setDiskUsagePattern( DiskUsagePattern.SWAP );
+            }
+            else if ( name.startsWith( "UPDATE" ) )
+            {
+                this.setDiskUsagePattern( DiskUsagePattern.UPDATE );
+            }
+        }
+    }
+
+    /**
+     * Number to send to disk at at time when memory is full.
+     * <p>
+     * @return int
+     */
+    @Override
+    public int getSpoolChunkSize()
+    {
+        return spoolChunkSize;
+    }
+
+    /**
+     * Number to send to disk at a time.
+     * <p>
+     * @param spoolChunkSize
+     */
+    @Override
+    public void setSpoolChunkSize( int spoolChunkSize )
+    {
+        this.spoolChunkSize = spoolChunkSize;
+    }
+
+    /**
+     * @return Returns the diskUsagePattern.
+     */
+    @Override
+    public DiskUsagePattern getDiskUsagePattern()
+    {
+        return diskUsagePattern;
+    }
+
+    /**
+     * Dumps the core attributes.
+     * <p>
+     * @return For debugging.
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder dump = new StringBuilder();
+
+        dump.append( "[ " );
+        dump.append( "useLateral = " ).append( useLateral );
+        dump.append( ", useRemote = " ).append( useRemote );
+        dump.append( ", useDisk = " ).append( useDisk );
+        dump.append( ", maxObjs = " ).append( maxObjs );
+        dump.append( ", maxSpoolPerRun = " ).append( maxSpoolPerRun );
+        dump.append( ", diskUsagePattern = " ).append( diskUsagePattern );
+        dump.append( ", spoolChunkSize = " ).append( spoolChunkSize );
+        dump.append( " ]" );
+
+        return dump.toString();
+    }
+
+    /**
+     * @see java.lang.Object#clone()
+     */
+    @Override
+    public ICompositeCacheAttributes clone()
+    {
+        try
+        {
+            return (ICompositeCacheAttributes)super.clone();
+        }
+        catch (CloneNotSupportedException e)
+        {
+            throw new RuntimeException("Clone not supported. This should never happen.", e);
+        }
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/ElementAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/ElementAttributes.java
new file mode 100644
index 0000000..077fc9c
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/ElementAttributes.java
@@ -0,0 +1,459 @@
+package org.apache.commons.jcs3.engine;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+import org.apache.commons.jcs3.engine.control.event.behavior.IElementEventHandler;
+
+/**
+ * This it the element attribute descriptor class. Each element in the cache has an ElementAttribute
+ * object associated with it. An ElementAttributes object can be associated with an element in 3
+ * ways:
+ * <ol>
+ * <li>When the item is put into the cache, you can associate an element attributes object.</li>
+ * <li>If not attributes object is include when the element is put into the cache, then the default
+ * attributes for the region will be used.</li>
+ * <li>The element attributes can be reset. This effectively results in a retrieval followed by a
+ * put. Hence, this is the same as 1.</li>
+ * </ol>
+ */
+public class ElementAttributes
+    implements IElementAttributes
+{
+    /** Don't change. */
+    private static final long serialVersionUID = 7814990748035017441L;
+
+    /** Can this item be flushed to disk */
+    private boolean IS_SPOOL = true;
+
+    /** Is this item laterally distributable */
+    private boolean IS_LATERAL = true;
+
+    /** Can this item be sent to the remote cache */
+    private boolean IS_REMOTE = true;
+
+    /**
+     * You can turn off expiration by setting this to true. This causes the cache to bypass both max
+     * life and idle time expiration.
+     */
+    private boolean IS_ETERNAL = true;
+
+    /** Max life seconds */
+    private long maxLife = -1;
+
+    /**
+     * The maximum time an entry can be idle. Setting this to -1 causes the idle time check to be
+     * ignored.
+     */
+    private long maxIdleTime = -1;
+
+    /** The byte size of the field. Must be manually set. */
+    private int size = 0;
+
+    /** The creation time. This is used to enforce the max life. */
+    private long createTime = 0;
+
+    /** The last access time. This is used to enforce the max idel time. */
+    private long lastAccessTime = 0;
+
+    /**
+     * The list of Event handlers to use. This is transient, since the event handlers cannot usually
+     * be serialized. This means that you cannot attach a post serialization event to an item.
+     * <p>
+     * TODO we need to check that when an item is passed to a non-local cache that if the local
+     * cache had a copy with event handlers, that those handlers are used.
+     */
+    private transient ArrayList<IElementEventHandler> eventHandlers;
+
+    private long timeFactor = 1000;
+
+    /**
+     * Constructor for the IElementAttributes object
+     */
+    public ElementAttributes()
+    {
+        this.createTime = System.currentTimeMillis();
+        this.lastAccessTime = this.createTime;
+    }
+
+    /**
+     * Constructor for the IElementAttributes object
+     * <p>
+     * @param attr
+     */
+    protected ElementAttributes( ElementAttributes attr )
+    {
+        IS_ETERNAL = attr.IS_ETERNAL;
+
+        // waterfall onto disk, for pure disk set memory to 0
+        IS_SPOOL = attr.IS_SPOOL;
+
+        // lateral
+        IS_LATERAL = attr.IS_LATERAL;
+
+        // central rmi store
+        IS_REMOTE = attr.IS_REMOTE;
+
+        maxLife = attr.maxLife;
+        // time-to-live
+        maxIdleTime = attr.maxIdleTime;
+        size = attr.size;
+    }
+
+    /**
+     * Sets the maxLife attribute of the IAttributes object.
+     * <p>
+     * @param mls The new MaxLifeSeconds value
+     */
+    @Override
+    public void setMaxLife(long mls)
+    {
+        this.maxLife = mls;
+    }
+
+    /**
+     * Sets the maxLife attribute of the IAttributes object. How many seconds it can live after
+     * creation.
+     * <p>
+     * If this is exceeded the element will not be returned, instead it will be removed. It will be
+     * removed on retrieval, or removed actively if the memory shrinker is turned on.
+     * @return The MaxLifeSeconds value
+     */
+    @Override
+    public long getMaxLife()
+    {
+        return this.maxLife;
+    }
+
+    /**
+     * Sets the idleTime attribute of the IAttributes object. This is the maximum time the item can
+     * be idle in the cache, that is not accessed.
+     * <p>
+     * If this is exceeded the element will not be returned, instead it will be removed. It will be
+     * removed on retrieval, or removed actively if the memory shrinker is turned on.
+     * @param idle The new idleTime value
+     */
+    @Override
+    public void setIdleTime( long idle )
+    {
+        this.maxIdleTime = idle;
+    }
+
+    /**
+     * Size in bytes. This is not used except in the admin pages. It will be 0 by default
+     * and is only updated when the element is serialized.
+     * <p>
+     * @param size The new size value
+     */
+    @Override
+    public void setSize( int size )
+    {
+        this.size = size;
+    }
+
+    /**
+     * Gets the size attribute of the IAttributes object
+     * <p>
+     * @return The size value
+     */
+    @Override
+    public int getSize()
+    {
+        return size;
+    }
+
+    /**
+     * Gets the createTime attribute of the IAttributes object.
+     * <p>
+     * This should be the current time in milliseconds returned by the sysutem call when the element
+     * is put in the cache.
+     * <p>
+     * Putting an item in the cache overrides any existing items.
+     * @return The createTime value
+     */
+    @Override
+    public long getCreateTime()
+    {
+        return createTime;
+    }
+
+    /**
+     * Sets the createTime attribute of the IElementAttributes object
+     */
+    public void setCreateTime()
+    {
+        createTime = System.currentTimeMillis();
+    }
+
+    /**
+     * Gets the idleTime attribute of the IAttributes object.
+     * <p>
+     * @return The idleTime value
+     */
+    @Override
+    public long getIdleTime()
+    {
+        return this.maxIdleTime;
+    }
+
+    /**
+     * Gets the time left to live of the IAttributes object.
+     * <p>
+     * This is the (max life + create time) - current time.
+     * @return The TimeToLiveSeconds value
+     */
+    @Override
+    public long getTimeToLiveSeconds()
+    {
+        final long now = System.currentTimeMillis();
+        final long timeFactorForMilliseconds = getTimeFactorForMilliseconds();
+        return ( this.getCreateTime() + this.getMaxLife() * timeFactorForMilliseconds - now ) / 1000;
+    }
+
+    /**
+     * Gets the LastAccess attribute of the IAttributes object.
+     * <p>
+     * @return The LastAccess value.
+     */
+    @Override
+    public long getLastAccessTime()
+    {
+        return this.lastAccessTime;
+    }
+
+    /**
+     * Sets the LastAccessTime as now of the IElementAttributes object
+     */
+    @Override
+    public void setLastAccessTimeNow()
+    {
+        this.lastAccessTime = System.currentTimeMillis();
+    }
+
+    /**
+     * only for use from test code
+     */
+    public void setLastAccessTime(long time)
+    {
+        this.lastAccessTime = time;
+    }
+
+    /**
+     * Can this item be spooled to disk
+     * <p>
+     * By default this is true.
+     * @return The spoolable value
+     */
+    @Override
+    public boolean getIsSpool()
+    {
+        return this.IS_SPOOL;
+    }
+
+    /**
+     * Sets the isSpool attribute of the IElementAttributes object
+     * <p>
+     * By default this is true.
+     * @param val The new isSpool value
+     */
+    @Override
+    public void setIsSpool( boolean val )
+    {
+        this.IS_SPOOL = val;
+    }
+
+    /**
+     * Is this item laterally distributable. Can it be sent to auxiliaries of type lateral.
+     * <p>
+     * By default this is true.
+     * @return The isLateral value
+     */
+    @Override
+    public boolean getIsLateral()
+    {
+        return this.IS_LATERAL;
+    }
+
+    /**
+     * Sets the isLateral attribute of the IElementAttributes object
+     * <p>
+     * By default this is true.
+     * @param val The new isLateral value
+     */
+    @Override
+    public void setIsLateral( boolean val )
+    {
+        this.IS_LATERAL = val;
+    }
+
+    /**
+     * Can this item be sent to the remote cache
+     * @return true if the item can be sent to a remote auxiliary
+     */
+    @Override
+    public boolean getIsRemote()
+    {
+        return this.IS_REMOTE;
+    }
+
+    /**
+     * Sets the isRemote attribute of the ElementAttributes object
+     * @param val The new isRemote value
+     */
+    @Override
+    public void setIsRemote( boolean val )
+    {
+        this.IS_REMOTE = val;
+    }
+
+    /**
+     * You can turn off expiration by setting this to true. The max life value will be ignored.
+     * <p>
+     * @return true if the item cannot expire.
+     */
+    @Override
+    public boolean getIsEternal()
+    {
+        return this.IS_ETERNAL;
+    }
+
+    /**
+     * Sets the isEternal attribute of the ElementAttributes object. True means that the item should
+     * never expire. If can still be removed if it is the least recently used, and you are using the
+     * LRUMemory cache. it just will not be filtered for expiration by the cache hub.
+     * <p>
+     * @param val The new isEternal value
+     */
+    @Override
+    public void setIsEternal( boolean val )
+    {
+        this.IS_ETERNAL = val;
+    }
+
+    /**
+     * Adds a ElementEventHandler. Handler's can be registered for multiple events. A registered
+     * handler will be called at every recognized event.
+     * <p>
+     * The alternative would be to register handlers for each event. Or maybe The handler interface
+     * should have a method to return whether it cares about certain events.
+     * <p>
+     * @param eventHandler The ElementEventHandler to be added to the list.
+     */
+    @Override
+    public void addElementEventHandler( IElementEventHandler eventHandler )
+    {
+        // lazy here, no concurrency problems expected
+        if ( this.eventHandlers == null )
+        {
+            this.eventHandlers = new ArrayList<>();
+        }
+        this.eventHandlers.add( eventHandler );
+    }
+
+    /**
+     * Sets the eventHandlers of the IElementAttributes object.
+     * <p>
+     * This add the references to the local list. Subsequent changes in the caller's list will not
+     * be reflected.
+     * <p>
+     * @param eventHandlers List of IElementEventHandler objects
+     */
+    @Override
+    public void addElementEventHandlers( List<IElementEventHandler> eventHandlers )
+    {
+        if ( eventHandlers == null )
+        {
+            return;
+        }
+
+        for (IElementEventHandler handler : eventHandlers)
+        {
+            addElementEventHandler(handler);
+        }
+    }
+
+    @Override
+    public long getTimeFactorForMilliseconds()
+    {
+        return timeFactor;
+    }
+
+    @Override
+    public void setTimeFactorForMilliseconds(long factor)
+    {
+        this.timeFactor = factor;
+    }
+
+    /**
+     * Gets the elementEventHandlers. Returns null if none exist. Makes checking easy.
+     * <p>
+     * @return The elementEventHandlers List of IElementEventHandler objects
+     */
+    @Override
+    public ArrayList<IElementEventHandler> getElementEventHandlers()
+    {
+        return this.eventHandlers;
+    }
+
+    /**
+     * For logging and debugging the element IElementAttributes.
+     * <p>
+     * @return String info about the values.
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder dump = new StringBuilder();
+
+        dump.append( "[ IS_LATERAL = " ).append( IS_LATERAL );
+        dump.append( ", IS_SPOOL = " ).append( IS_SPOOL );
+        dump.append( ", IS_REMOTE = " ).append( IS_REMOTE );
+        dump.append( ", IS_ETERNAL = " ).append( IS_ETERNAL );
+        dump.append( ", MaxLifeSeconds = " ).append( this.getMaxLife() );
+        dump.append( ", IdleTime = " ).append( this.getIdleTime() );
+        dump.append( ", CreateTime = " ).append( this.getCreateTime() );
+        dump.append( ", LastAccessTime = " ).append( this.getLastAccessTime() );
+        dump.append( ", getTimeToLiveSeconds() = " ).append( String.valueOf( getTimeToLiveSeconds() ) );
+        dump.append( ", createTime = " ).append( String.valueOf( createTime ) ).append( " ]" );
+
+        return dump.toString();
+    }
+
+    /**
+     * @see java.lang.Object#clone()
+     */
+    @Override
+    public IElementAttributes clone()
+    {
+        try
+        {
+        	ElementAttributes c = (ElementAttributes) super.clone();
+        	c.setCreateTime();
+            return c;
+        }
+        catch (CloneNotSupportedException e)
+        {
+            throw new RuntimeException("Clone not supported. This should never happen.", e);
+        }
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/PooledCacheEventQueue.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/PooledCacheEventQueue.java
new file mode 100644
index 0000000..5fdfc0d
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/PooledCacheEventQueue.java
@@ -0,0 +1,191 @@
+package org.apache.commons.jcs3.engine;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ThreadPoolExecutor;
+
+import org.apache.commons.jcs3.engine.behavior.ICacheListener;
+import org.apache.commons.jcs3.engine.stats.StatElement;
+import org.apache.commons.jcs3.engine.stats.Stats;
+import org.apache.commons.jcs3.engine.stats.behavior.IStatElement;
+import org.apache.commons.jcs3.engine.stats.behavior.IStats;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.threadpool.ThreadPoolManager;
+
+/**
+ * An event queue is used to propagate ordered cache events to one and only one target listener.
+ * <p>
+ * This is a modified version of the experimental version. It uses a PooledExecutor and a
+ * BoundedBuffer to queue up events and execute them as threads become available.
+ * <p>
+ * The PooledExecutor is static, because presumably these processes will be IO bound, so throwing
+ * more than a few threads at them will serve no purpose other than to saturate the IO interface. In
+ * light of this, having one thread per region seems unnecessary. This may prove to be false.
+ */
+public class PooledCacheEventQueue<K, V>
+    extends AbstractCacheEventQueue<K, V>
+{
+    /** The logger. */
+    private static final Log log = LogManager.getLog( PooledCacheEventQueue.class );
+
+    /** The type of event queue */
+    private static final QueueType queueType = QueueType.POOLED;
+
+    /** The Thread Pool to execute events with. */
+    protected ExecutorService pool = null;
+
+    /** The Thread Pool queue */
+    protected BlockingQueue<Runnable> queue = null;
+
+    /**
+     * Constructor for the CacheEventQueue object
+     * <p>
+     * @param listener
+     * @param listenerId
+     * @param cacheName
+     * @param maxFailure
+     * @param waitBeforeRetry
+     * @param threadPoolName
+     */
+    public PooledCacheEventQueue( ICacheListener<K, V> listener, long listenerId, String cacheName, int maxFailure,
+                                  int waitBeforeRetry, String threadPoolName )
+    {
+        initialize( listener, listenerId, cacheName, maxFailure, waitBeforeRetry, threadPoolName );
+    }
+
+    /**
+     * Initializes the queue.
+     * <p>
+     * @param listener
+     * @param listenerId
+     * @param cacheName
+     * @param maxFailure
+     * @param waitBeforeRetry
+     * @param threadPoolName
+     */
+    protected void initialize( ICacheListener<K, V> listener, long listenerId, String cacheName, int maxFailure,
+                            int waitBeforeRetry, String threadPoolName )
+    {
+        super.initialize(listener, listenerId, cacheName, maxFailure, waitBeforeRetry);
+
+        // this will share the same pool with other event queues by default.
+        pool = ThreadPoolManager.getInstance().getExecutorService(
+                (threadPoolName == null) ? "cache_event_queue" : threadPoolName );
+
+        if (pool instanceof ThreadPoolExecutor)
+        {
+        	queue = ((ThreadPoolExecutor) pool).getQueue();
+        }
+    }
+
+    /**
+     * @return the queue type
+     */
+    @Override
+    public QueueType getQueueType()
+    {
+        return queueType;
+    }
+
+    /**
+     * Destroy the queue. Interrupt all threads.
+     */
+    @Override
+    public synchronized void destroy()
+    {
+        if ( isWorking() )
+        {
+            setWorking(false);
+            pool.shutdownNow();
+            log.info( "Cache event queue destroyed: {0}", this );
+        }
+    }
+
+    /**
+     * Adds an event to the queue.
+     * <p>
+     * @param event
+     */
+    @Override
+    protected void put( AbstractCacheEvent event )
+    {
+        pool.execute( event );
+    }
+
+    /**
+     * @return IStats
+     */
+    @Override
+    public IStats getStatistics()
+    {
+        IStats stats = new Stats();
+        stats.setTypeName( "Pooled Cache Event Queue" );
+
+        ArrayList<IStatElement<?>> elems = new ArrayList<>();
+
+        elems.add(new StatElement<>( "Working", Boolean.valueOf(isWorking()) ) );
+        elems.add(new StatElement<>( "Empty", Boolean.valueOf(this.isEmpty()) ) );
+
+        if ( queue != null )
+        {
+            elems.add(new StatElement<>( "Queue Size", Integer.valueOf(queue.size()) ) );
+            elems.add(new StatElement<>( "Queue Capacity", Integer.valueOf(queue.remainingCapacity()) ) );
+        }
+
+        stats.setStatElements( elems );
+
+        return stats;
+    }
+
+    /**
+     * If the Queue is using a bounded channel we can determine the size. If it is zero or we can't
+     * determine the size, we return true.
+     * <p>
+     * @return whether or not there are items in the queue
+     */
+    @Override
+    public boolean isEmpty()
+    {
+        return size() == 0;
+    }
+
+    /**
+     * Returns the number of elements in the queue. If the queue cannot determine the size
+     * accurately it will return 0.
+     * <p>
+     * @return number of items in the queue.
+     */
+    @Override
+    public int size()
+    {
+        if ( queue == null )
+        {
+            return 0;
+        }
+        else
+        {
+            return queue.size();
+        }
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/ZombieCacheService.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/ZombieCacheService.java
new file mode 100644
index 0000000..5f4ac84
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/ZombieCacheService.java
@@ -0,0 +1,151 @@
+package org.apache.commons.jcs3.engine;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheService;
+import org.apache.commons.jcs3.engine.behavior.IZombie;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * Zombie adapter for any cache service. Balks at every call.
+ */
+public class ZombieCacheService<K, V>
+    implements ICacheService<K, V>, IZombie
+{
+    /** The logger. */
+    private static final Log log = LogManager.getLog( ZombieCacheService.class );
+
+    /**
+     * @param item
+     */
+    public void put( ICacheElement<K, V> item )
+    {
+        log.debug( "Zombie put for item {0}", item );
+        // zombies have no inner life
+    }
+
+    /**
+     * Does nothing.
+     * <p>
+     * @param item
+     */
+    @Override
+    public void update( ICacheElement<K, V> item )
+    {
+        // zombies have no inner life
+    }
+
+    /**
+     * @param cacheName
+     * @param key
+     * @return null. zombies have no internal data
+     */
+    @Override
+    public ICacheElement<K, V> get( String cacheName, K key )
+    {
+        return null;
+    }
+
+    /**
+     * Returns an empty map. Zombies have no internal data.
+     * <p>
+     * @param cacheName
+     * @param keys
+     * @return Collections.EMPTY_MAP
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys )
+    {
+        return Collections.emptyMap();
+    }
+
+    /**
+     * Returns an empty map. Zombies have no internal data.
+     * <p>
+     * @param cacheName
+     * @param pattern
+     * @return Collections.EMPTY_MAP
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern )
+    {
+        return Collections.emptyMap();
+    }
+
+    /**
+     * Logs the get to debug, but always balks.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @param container
+     * @return null always
+     */
+    public Serializable get( String cacheName, K key, boolean container )
+    {
+        log.debug( "Zombie get for key [{0}] cacheName [{1}] container [{2}]",
+                key, cacheName, container);
+        // zombies have no inner life
+        return null;
+    }
+
+    /**
+     * @param cacheName
+     * @param key
+     */
+    @Override
+    public void remove( String cacheName, K key )
+    {
+        // zombies have no inner life
+    }
+
+    /**
+     * @param cacheName
+     */
+    @Override
+    public void removeAll( String cacheName )
+    {
+        // zombies have no inner life
+    }
+
+    /**
+     * @param cacheName
+     */
+    @Override
+    public void dispose( String cacheName )
+    {
+        // zombies have no inner life
+    }
+
+    /**
+     * Frees all caches.
+     */
+    @Override
+    public void release()
+    {
+        // zombies have no inner life
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/ZombieCacheServiceNonLocal.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/ZombieCacheServiceNonLocal.java
new file mode 100644
index 0000000..10cd1a7
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/ZombieCacheServiceNonLocal.java
@@ -0,0 +1,316 @@
+package org.apache.commons.jcs3.engine;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheServiceNonLocal;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.timing.ElapsedTimer;
+
+/**
+ * Zombie adapter for the non local cache services. It just balks if there is no queue configured.
+ * <p>
+ * If a queue is configured, then events will be added to the queue. The idea is that when proper
+ * operation is restored, the non local cache will walk the queue. The queue must be bounded so it
+ * does not eat memory.
+ * <p>
+ * This originated in the remote cache.
+ */
+public class ZombieCacheServiceNonLocal<K, V>
+    extends ZombieCacheService<K, V>
+    implements ICacheServiceNonLocal<K, V>
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog( ZombieCacheServiceNonLocal.class );
+
+    /** How big can the queue grow. */
+    private int maxQueueSize = 0;
+
+    /** The queue */
+    private final ConcurrentLinkedQueue<ZombieEvent> queue;
+
+    /**
+     * Default.
+     */
+    public ZombieCacheServiceNonLocal()
+    {
+        queue = new ConcurrentLinkedQueue<>();
+    }
+
+    /**
+     * Sets the maximum number of items that will be allowed on the queue.
+     * <p>
+     * @param maxQueueSize
+     */
+    public ZombieCacheServiceNonLocal( int maxQueueSize )
+    {
+        this.maxQueueSize = maxQueueSize;
+        queue = new ConcurrentLinkedQueue<>();
+    }
+
+    /**
+     * Gets the number of items on the queue.
+     * <p>
+     * @return size of the queue.
+     */
+    public int getQueueSize()
+    {
+        return queue.size();
+    }
+
+    private void addQueue(ZombieEvent event)
+    {
+        queue.add(event);
+        if (queue.size() > maxQueueSize)
+        {
+            queue.poll(); // drop oldest entry
+        }
+    }
+
+    /**
+     * Adds an update event to the queue if the maxSize is greater than 0;
+     * <p>
+     * @param item ICacheElement
+     * @param listenerId - identifies the caller.
+     */
+    @Override
+    public void update( ICacheElement<K, V> item, long listenerId )
+    {
+        if ( maxQueueSize > 0 )
+        {
+            PutEvent<K, V> event = new PutEvent<>( item, listenerId );
+            addQueue( event );
+        }
+        // Zombies have no inner life
+    }
+
+    /**
+     * Adds a removeAll event to the queue if the maxSize is greater than 0;
+     * <p>
+     * @param cacheName - region name
+     * @param key - item key
+     * @param listenerId - identifies the caller.
+     */
+    @Override
+    public void remove( String cacheName, K key, long listenerId )
+    {
+        if ( maxQueueSize > 0 )
+        {
+            RemoveEvent<K> event = new RemoveEvent<>( cacheName, key, listenerId );
+            addQueue( event );
+        }
+        // Zombies have no inner life
+    }
+
+    /**
+     * Adds a removeAll event to the queue if the maxSize is greater than 0;
+     * <p>
+     * @param cacheName - name of the region
+     * @param listenerId - identifies the caller.
+     */
+    @Override
+    public void removeAll( String cacheName, long listenerId )
+    {
+        if ( maxQueueSize > 0 )
+        {
+            RemoveAllEvent event = new RemoveAllEvent( cacheName, listenerId );
+            addQueue( event );
+        }
+        // Zombies have no inner life
+    }
+
+    /**
+     * Does nothing. Gets are synchronous and cannot be added to a queue.
+     * <p>
+     * @param cacheName - region name
+     * @param key - item key
+     * @param requesterId - identifies the caller.
+     * @return null
+     * @throws IOException
+     */
+    @Override
+    public ICacheElement<K, V> get( String cacheName, K key, long requesterId )
+        throws IOException
+    {
+        // Zombies have no inner life
+        return null;
+    }
+
+    /**
+     * Does nothing.
+     * <p>
+     * @param cacheName
+     * @param pattern
+     * @param requesterId
+     * @return empty map
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern, long requesterId )
+        throws IOException
+    {
+        return Collections.emptyMap();
+    }
+
+    /**
+     * @param cacheName - region name
+     * @param keys - item key
+     * @param requesterId - identity of the caller
+     * @return an empty map. zombies have no internal data
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys, long requesterId )
+    {
+        return new HashMap<>();
+    }
+
+    /**
+     * Does nothing.
+     * <p>
+     * @param cacheName - region name
+     * @return empty set
+     */
+    @Override
+    public Set<K> getKeySet( String cacheName )
+    {
+        return Collections.emptySet();
+    }
+
+    /**
+     * Walk the queue, calling the service for each queue operation.
+     * <p>
+     * @param service
+     * @throws Exception
+     */
+    public synchronized void propagateEvents( ICacheServiceNonLocal<K, V> service )
+        throws Exception
+    {
+        int cnt = 0;
+        log.info( "Propagating events to the new ICacheServiceNonLocal." );
+        ElapsedTimer timer = new ElapsedTimer();
+        while ( !queue.isEmpty() )
+        {
+            cnt++;
+
+            // for each item, call the appropriate service method
+            ZombieEvent event = queue.poll();
+
+            if ( event instanceof PutEvent )
+            {
+                @SuppressWarnings("unchecked") // Type checked by instanceof
+                PutEvent<K, V> putEvent = (PutEvent<K, V>) event;
+                service.update( putEvent.element, event.requesterId );
+            }
+            else if ( event instanceof RemoveEvent )
+            {
+                @SuppressWarnings("unchecked") // Type checked by instanceof
+                RemoveEvent<K> removeEvent = (RemoveEvent<K>) event;
+                service.remove( event.cacheName, removeEvent.key, event.requesterId );
+            }
+            else if ( event instanceof RemoveAllEvent )
+            {
+                service.removeAll( event.cacheName, event.requesterId );
+            }
+        }
+        log.info( "Propagated {0} events to the new ICacheServiceNonLocal in {1}",
+                cnt, timer.getElapsedTimeString() );
+    }
+
+    /**
+     * Base of the other events.
+     */
+    protected static abstract class ZombieEvent
+    {
+        /** The name of the region. */
+        String cacheName;
+
+        /** The id of the requester */
+        long requesterId;
+    }
+
+    /**
+     * A basic put event.
+     */
+    private static class PutEvent<K, V>
+        extends ZombieEvent
+    {
+        /** The element to put */
+        ICacheElement<K, V> element;
+
+        /**
+         * Set the element
+         * @param element
+         * @param requesterId
+         */
+        public PutEvent( ICacheElement<K, V> element, long requesterId )
+        {
+            this.requesterId = requesterId;
+            this.element = element;
+        }
+    }
+
+    /**
+     * A basic Remove event.
+     */
+    private static class RemoveEvent<K>
+        extends ZombieEvent
+    {
+        /** The key to remove */
+        K key;
+
+        /**
+         * Set the element
+         * @param cacheName
+         * @param key
+         * @param requesterId
+         */
+        public RemoveEvent( String cacheName, K key, long requesterId )
+        {
+            this.cacheName = cacheName;
+            this.requesterId = requesterId;
+            this.key = key;
+        }
+    }
+
+    /**
+     * A basic RemoveAll event.
+     */
+    private static class RemoveAllEvent
+        extends ZombieEvent
+    {
+        /**
+         * @param cacheName
+         * @param requesterId
+         */
+        public RemoveAllEvent( String cacheName, long requesterId )
+        {
+            this.cacheName = cacheName;
+            this.requesterId = requesterId;
+        }
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/ZombieCacheWatch.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/ZombieCacheWatch.java
new file mode 100644
index 0000000..8fbac80
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/ZombieCacheWatch.java
@@ -0,0 +1,73 @@
+package org.apache.commons.jcs3.engine;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.engine.behavior.ICacheListener;
+import org.apache.commons.jcs3.engine.behavior.ICacheObserver;
+import org.apache.commons.jcs3.engine.behavior.IZombie;
+
+/**
+ * Zombie Observer.
+ */
+public class ZombieCacheWatch
+    implements ICacheObserver, IZombie
+{
+    /**
+     * Adds a feature to the CacheListener attribute of the ZombieCacheWatch object
+     * <p>
+     * @param cacheName The feature to be added to the CacheListener attribute
+     * @param obj The feature to be added to the CacheListener attribute
+     */
+    @Override
+    public <K, V> void addCacheListener( String cacheName, ICacheListener<K, V> obj )
+    {
+        // empty
+    }
+
+    /**
+     * Adds a feature to the CacheListener attribute of the ZombieCacheWatch object
+     * <p>
+     * @param obj The feature to be added to the CacheListener attribute
+     */
+    @Override
+    public <K, V> void addCacheListener( ICacheListener<K, V> obj )
+    {
+        // empty
+    }
+
+    /**
+     * @param cacheName
+     * @param obj
+     */
+    @Override
+    public <K, V> void removeCacheListener( String cacheName, ICacheListener<K, V> obj )
+    {
+        // empty
+    }
+
+    /**
+     * @param obj
+     */
+    @Override
+    public <K, V> void removeCacheListener( ICacheListener<K, V> obj )
+    {
+        // empty
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICache.java
new file mode 100644
index 0000000..8735fca
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICache.java
@@ -0,0 +1,144 @@
+package org.apache.commons.jcs3.engine.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.engine.CacheStatus;
+import org.apache.commons.jcs3.engine.match.behavior.IKeyMatcher;
+
+/**
+ * This is the top level interface for all cache like structures. It defines the methods used
+ * internally by JCS to access, modify, and instrument such structures.
+ *
+ * This allows for a suite of reusable components for accessing such structures, for example
+ * asynchronous access via an event queue.
+ */
+public interface ICache<K, V>
+    extends ICacheType
+{
+    /** Delimiter of a cache name component. This is used for hierarchical deletion */
+    String NAME_COMPONENT_DELIMITER = ":";
+
+    /**
+     * Puts an item to the cache.
+     *
+     * @param element
+     * @throws IOException
+     */
+    void update( ICacheElement<K, V> element )
+        throws IOException;
+
+    /**
+     * Gets an item from the cache.
+     *
+     * @param key
+     * @return a cache element, or null if there is no data in cache for this key
+     * @throws IOException
+     */
+    ICacheElement<K, V> get( K key )
+        throws IOException;
+
+    /**
+     * Gets multiple items from the cache based on the given set of keys.
+     *
+     * @param keys
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no data in cache for any of these keys
+     * @throws IOException
+     */
+    Map<K, ICacheElement<K, V>> getMultiple(Set<K> keys)
+        throws IOException;
+
+    /**
+     * Gets items from the cache matching the given pattern.  Items from memory will replace those from remote sources.
+     *
+     * This only works with string keys.  It's too expensive to do a toString on every key.
+     *
+     * Auxiliaries will do their best to handle simple expressions.  For instance, the JDBC disk cache will convert * to % and . to _
+     *
+     * @param pattern
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no data matching the pattern.
+     * @throws IOException
+     */
+    Map<K, ICacheElement<K, V>> getMatching(String pattern)
+        throws IOException;
+
+    /**
+     * Removes an item from the cache.
+     *
+     * @param key
+     * @return false if there was an error in removal
+     * @throws IOException
+     */
+    boolean remove( K key )
+        throws IOException;
+
+    /**
+     * Removes all cached items from the cache.
+     *
+     * @throws IOException
+     */
+    void removeAll()
+        throws IOException;
+
+    /**
+     * Prepares for shutdown.
+     * @throws IOException
+     */
+    void dispose()
+        throws IOException;
+
+    /**
+     * Returns the current cache size in number of elements.
+     *
+     * @return number of elements
+     */
+    int getSize();
+
+    /**
+     * Returns the cache status.
+     *
+     * @return Alive or Error
+     */
+    CacheStatus getStatus();
+
+    /**
+     * Returns the cache stats.
+     *
+     * @return String of important historical information.
+     */
+    String getStats();
+
+    /**
+     * Returns the cache name.
+     *
+     * @return usually the region name.
+     */
+    String getCacheName();
+
+    /**
+     * Sets the key matcher used by get matching.
+     *
+     * @param keyMatcher
+     */
+    void setKeyMatcher( IKeyMatcher<K> keyMatcher );
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICacheElement.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICacheElement.java
new file mode 100644
index 0000000..e094212
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICacheElement.java
@@ -0,0 +1,74 @@
+package org.apache.commons.jcs3.engine.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.Serializable;
+
+/**
+ * Every item is the cache is wrapped in an ICacheElement. This contains
+ * information about the element: the region name, the key, the value, and the
+ * element attributes.
+ * <p>
+ * The element attributes have lots of useful information about each element,
+ * such as when they were created, how long they have to live, and if they are
+ * allowed to be spooled, etc.
+ *
+ */
+public interface ICacheElement<K, V>
+    extends Serializable
+{
+
+    /**
+     * Gets the cacheName attribute of the ICacheElement&lt;K, V&gt; object. The cacheName
+     * is also known as the region name.
+     *
+     * @return The cacheName value
+     */
+    String getCacheName();
+
+    /**
+     * Gets the key attribute of the ICacheElement&lt;K, V&gt; object
+     *
+     * @return The key value
+     */
+    K getKey();
+
+    /**
+     * Gets the val attribute of the ICacheElement&lt;K, V&gt; object
+     *
+     * @return The val value
+     */
+    V getVal();
+
+    /**
+     * Gets the attributes attribute of the ICacheElement&lt;K, V&gt; object
+     *
+     * @return The attributes value
+     */
+    IElementAttributes getElementAttributes();
+
+    /**
+     * Sets the attributes attribute of the ICacheElement&lt;K, V&gt; object
+     *
+     * @param attr
+     *            The new attributes value
+     */
+    void setElementAttributes( IElementAttributes attr );
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICacheElementSerialized.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICacheElementSerialized.java
new file mode 100644
index 0000000..917394d
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICacheElementSerialized.java
@@ -0,0 +1,41 @@
+package org.apache.commons.jcs3.engine.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * This interface defines the behavior of the serialized element wrapper.
+ * <p>
+ * The value is stored as a byte array. This should allow for a variety of serialization mechanisms.
+ * <p>
+ * This currently extends ICacheElement&lt;K, V&gt; for backward compatibility.
+ *<p>
+ * @author Aaron Smuts
+ */
+public interface ICacheElementSerialized<K, V>
+    extends ICacheElement<K, V>
+{
+    /**
+     * Gets the value attribute of the ICacheElementSerialized object. This is the value the client
+     * cached serialized by some mechanism.
+     *<p>
+     * @return The serialized value
+     */
+    byte[] getSerializedValue();
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICacheEventQueue.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICacheEventQueue.java
new file mode 100644
index 0000000..7fec6ed
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICacheEventQueue.java
@@ -0,0 +1,125 @@
+package org.apache.commons.jcs3.engine.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+
+import org.apache.commons.jcs3.engine.stats.behavior.IStats;
+
+/**
+ * Interface for a cache event queue. An event queue is used to propagate
+ * ordered cache events to one and only one target listener.
+ */
+public interface ICacheEventQueue<K, V>
+{
+    enum QueueType
+    {
+        /** Does not use a thread pool. */
+        SINGLE,
+
+        /** Uses a thread pool. */
+        POOLED
+    }
+
+    /**
+     * Return the type of event queue we are using, either single or pooled.
+     * <p>
+     * @return the queue type: single or pooled
+     */
+    QueueType getQueueType();
+
+    /**
+     * Adds a feature to the PutEvent attribute of the ICacheEventQueue object
+     * <p>
+     * @param ce
+     *            The feature to be added to the PutEvent attribute
+     * @throws IOException
+     */
+    void addPutEvent( ICacheElement<K, V> ce )
+        throws IOException;
+
+    /**
+     * Adds a feature to the RemoveEvent attribute of the ICacheEventQueue
+     * object
+     * <p>
+     * @param key
+     *            The feature to be added to the RemoveEvent attribute
+     * @throws IOException
+     */
+    void addRemoveEvent( K key )
+        throws IOException;
+
+    /**
+     * Adds a feature to the RemoveAllEvent attribute of the ICacheEventQueue
+     * object
+     * <p>
+     * @throws IOException
+     */
+    void addRemoveAllEvent()
+        throws IOException;
+
+    /**
+     * Adds a feature to the DisposeEvent attribute of the ICacheEventQueue
+     * object
+     * <p>
+     * @throws IOException
+     */
+    void addDisposeEvent()
+        throws IOException;
+
+    /**
+     * Gets the listenerId attribute of the ICacheEventQueue object
+     *
+     * @return The listenerId value
+     */
+    long getListenerId();
+
+    /** Description of the Method */
+    void destroy();
+
+    /**
+     * A Queue is working unless it has reached its max failure count.
+     * <p>
+     * @return boolean
+     */
+    boolean isWorking();
+
+    /**
+     * Returns the number of elements in the queue.  If the queue cannot
+     * determine the size accurately it will return 1.
+     * <p>
+     * @return number of items in the queue.
+     */
+    int size();
+
+    /**
+     * Are there elements in the queue.
+     * <p>
+     * @return true if there are stil elements.
+     */
+    boolean isEmpty();
+
+    /**
+     * Returns the historical and statistical data for an event queue cache.
+     * <p>
+     * @return IStats
+     */
+    IStats getStatistics();
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICacheListener.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICacheListener.java
new file mode 100644
index 0000000..2c98b91
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICacheListener.java
@@ -0,0 +1,86 @@
+package org.apache.commons.jcs3.engine.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+
+/**
+ * Used to receive a cache event notification.
+ * <p>
+ * Note: objects which implement this interface are local listeners to cache changes, whereas
+ * objects which implement IRmiCacheListener are remote listeners to cache changes.
+ */
+public interface ICacheListener<K, V>
+{
+    /**
+     * Notifies the subscribers for a cache entry update.
+     * <p>
+     * @param item
+     * @throws IOException
+     */
+    void handlePut( ICacheElement<K, V> item )
+        throws IOException;
+
+    /**
+     * Notifies the subscribers for a cache entry removal.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @throws IOException
+     */
+    void handleRemove( String cacheName, K key )
+        throws IOException;
+
+    /**
+     * Notifies the subscribers for a cache remove-all.
+     * <p>
+     * @param cacheName
+     * @throws IOException
+     */
+    void handleRemoveAll( String cacheName )
+        throws IOException;
+
+    /**
+     * Notifies the subscribers for freeing up the named cache.
+     * <p>
+     * @param cacheName
+     * @throws IOException
+     */
+    void handleDispose( String cacheName )
+        throws IOException;
+
+    /**
+     * sets unique identifier of listener home
+     * <p>
+     * @param id The new listenerId value
+     * @throws IOException
+     */
+    void setListenerId( long id )
+        throws IOException;
+
+    /**
+     * Gets the listenerId attribute of the ICacheListener object
+     * <p>
+     * @return The listenerId value
+     * @throws IOException
+     */
+    long getListenerId()
+        throws IOException;
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICacheObserver.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICacheObserver.java
new file mode 100644
index 0000000..dfb85ff
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICacheObserver.java
@@ -0,0 +1,78 @@
+package org.apache.commons.jcs3.engine.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+
+/**
+ * Used to register interest in receiving cache changes. <br>
+ * <br>
+ * Note: server which implements this interface provides a local cache event
+ * notification service, whereas server which implements IRmiCacheWatch provides
+ * a remote cache event notification service.
+ *
+ */
+public interface ICacheObserver
+{
+    /**
+     * Subscribes to the specified cache.
+     *
+     * @param cacheName
+     *            the specified cache.
+     * @param obj
+     *            object to notify for cache changes.
+     * @throws IOException
+     */
+    <K, V> void addCacheListener( String cacheName, ICacheListener<K, V> obj )
+        throws IOException;
+
+    //, CacheNotFoundException;
+
+    /**
+     * Subscribes to all caches.
+     *
+     * @param obj
+     *            object to notify for all cache changes.
+     * @throws IOException
+     */
+    <K, V> void addCacheListener( ICacheListener<K, V> obj )
+        throws IOException;
+
+    /**
+     * Unsubscribes from the specified cache.
+     * @param cacheName
+     *
+     * @param obj
+     *            existing subscriber.
+     * @throws IOException
+     */
+    <K, V> void removeCacheListener( String cacheName, ICacheListener<K, V> obj )
+        throws IOException;
+
+    /**
+     * Unsubscribes from all caches.
+     *
+     * @param obj
+     *            existing subscriber.
+     * @throws IOException
+     */
+    <K, V> void removeCacheListener( ICacheListener<K, V> obj )
+        throws IOException;
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICacheService.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICacheService.java
new file mode 100644
index 0000000..8900eef
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICacheService.java
@@ -0,0 +1,117 @@
+package org.apache.commons.jcs3.engine.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.access.exception.ObjectExistsException;
+import org.apache.commons.jcs3.access.exception.ObjectNotFoundException;
+
+/**
+ * Used to retrieve and update the cache.
+ * <p>
+ * Note: server which implements this interface provides a local cache service, whereas server which
+ * implements IRmiCacheService provides a remote cache service.
+ */
+public interface ICacheService<K, V>
+{
+    /**
+     * Puts a cache item to the cache.
+     * <p>
+     * @param item
+     * @throws ObjectExistsException
+     * @throws IOException
+     */
+    void update( ICacheElement<K, V> item )
+        throws ObjectExistsException, IOException;
+
+    /**
+     * Returns a cache bean from the specified cache; or null if the key does not exist.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @return the ICacheElement&lt;K, V&gt; or null if not found
+     * @throws ObjectNotFoundException
+     * @throws IOException
+     */
+    ICacheElement<K, V> get( String cacheName, K key )
+        throws ObjectNotFoundException, IOException;
+
+    /**
+     * Gets multiple items from the cache based on the given set of keys.
+     * <p>
+     * @param cacheName
+     * @param keys
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache for any of these keys
+     * @throws ObjectNotFoundException
+     * @throws IOException
+     */
+    Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys )
+        throws ObjectNotFoundException, IOException;
+
+    /**
+     * Gets multiple items from the cache matching the pattern.
+     * <p>
+     * @param cacheName
+     * @param pattern
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache matching the pattern.
+     * @throws IOException
+     */
+    Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern )
+        throws IOException;
+
+    /**
+     * Removes the given key from the specified cache.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @throws IOException
+     */
+    void remove( String cacheName, K key )
+        throws IOException;
+
+    /**
+     * Remove all keys from the specified cache.
+     * @param cacheName
+     * @throws IOException
+     */
+    void removeAll( String cacheName )
+        throws IOException;
+
+    /**
+     * Frees the specified cache.
+     * <p>
+     * @param cacheName
+     * @throws IOException
+     */
+    void dispose( String cacheName )
+        throws IOException;
+
+    /**
+     * Frees all caches.
+     * @throws IOException
+     */
+    void release()
+        throws IOException;
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICacheServiceAdmin.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICacheServiceAdmin.java
new file mode 100644
index 0000000..5c5a012
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICacheServiceAdmin.java
@@ -0,0 +1,51 @@
+package org.apache.commons.jcs3.engine.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+
+/**
+ * Description of the Interface
+ *
+ */
+public interface ICacheServiceAdmin
+{
+
+    /**
+     * Gets the stats attribute of the ICacheServiceAdmin object
+     *
+     * @return The stats value
+     * @throws IOException
+     */
+    String getStats()
+        throws IOException;
+
+    /** Description of the Method
+     * @throws IOException*/
+    void shutdown()
+        throws IOException;
+
+    /** Description of the Method
+     * @param host
+     * @param port
+     * @throws IOException*/
+    void shutdown( String host, int port )
+        throws IOException;
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICacheServiceNonLocal.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICacheServiceNonLocal.java
new file mode 100644
index 0000000..82041af
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICacheServiceNonLocal.java
@@ -0,0 +1,118 @@
+package org.apache.commons.jcs3.engine.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.rmi.Remote;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Used to retrieve and update non local caches, such as the remote and lateral caches. Unlike
+ * ICacheService, the methods here have a requester id. This allows us to avoid propagating events
+ * to ourself.
+ * <p>
+ * TODO consider not extending ICacheService
+ */
+public interface ICacheServiceNonLocal<K, V>
+    extends Remote, ICacheService<K, V>
+{
+    /**
+     * Puts a cache item to the cache.
+     * <p>
+     * @param item
+     * @param requesterId
+     * @throws IOException
+     */
+    void update( ICacheElement<K, V> item, long requesterId )
+        throws IOException;
+
+    /**
+     * Removes the given key from the specified cache.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @param requesterId
+     * @throws IOException
+     */
+    void remove( String cacheName, K key, long requesterId )
+        throws IOException;
+
+    /**
+     * Remove all keys from the specified cache.
+     * <p>
+     * @param cacheName
+     * @param requesterId
+     * @throws IOException
+     */
+    void removeAll( String cacheName, long requesterId )
+        throws IOException;
+
+    /**
+     * Returns a cache bean from the specified cache; or null if the key does not exist.
+     * <p>
+     * Adding the requester id, allows the cache to determine the source of the get.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @param requesterId
+     * @return ICacheElement
+     * @throws IOException
+     */
+    ICacheElement<K, V> get( String cacheName, K key, long requesterId )
+        throws IOException;
+
+    /**
+     * Gets multiple items from the cache based on the given set of keys.
+     * <p>
+     * @param cacheName
+     * @param keys
+     * @param requesterId
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache for any of these keys
+     * @throws IOException
+     */
+    Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys, long requesterId )
+        throws IOException;
+
+    /**
+     * Gets multiple items from the cache matching the pattern.
+     * <p>
+     * @param cacheName
+     * @param pattern
+     * @param requesterId
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache matching the pattern.
+     * @throws IOException
+     */
+    Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern, long requesterId )
+        throws IOException;
+
+    /**
+     * Get a set of the keys for all elements in the cache.
+     * <p>
+     * @param cacheName the name of the cache
+     * @return a set of the key type
+     * TODO This should probably be done in chunks with a range passed in. This
+     *       will be a problem if someone puts a 1,000,000 or so items in a
+     *       region.
+     */
+    Set<K> getKeySet( String cacheName ) throws IOException;
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICacheType.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICacheType.java
new file mode 100644
index 0000000..1012ab8
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICacheType.java
@@ -0,0 +1,49 @@
+package org.apache.commons.jcs3.engine.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Interface implemented by a specific cache.
+ *
+ */
+public interface ICacheType
+{
+    enum CacheType {
+        /** Composite/ memory cache type, central hub. */
+        CACHE_HUB,
+
+        /** Disk cache type. */
+        DISK_CACHE,
+
+        /** Lateral cache type. */
+        LATERAL_CACHE,
+
+        /** Remote cache type. */
+        REMOTE_CACHE
+    }
+
+    /**
+     * Returns the cache type.
+     * <p>
+     * @return The cacheType value
+     */
+    CacheType getCacheType();
+
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICompositeCacheAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICompositeCacheAttributes.java
new file mode 100644
index 0000000..57451fa
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICompositeCacheAttributes.java
@@ -0,0 +1,244 @@
+package org.apache.commons.jcs3.engine.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.Serializable;
+
+/**
+ * This defines the minimal behavior for the Cache Configuration settings.
+ */
+public interface ICompositeCacheAttributes
+    extends Serializable, Cloneable
+{
+    enum DiskUsagePattern
+    {
+        /** Items will only go to disk when the memory limit is reached. This is the default. */
+        SWAP,
+
+        /**
+         * Items will go to disk on a normal put. If The disk usage pattern is UPDATE, the swap will be
+         * disabled.
+         */
+        UPDATE
+    }
+
+    /**
+     * SetMaxObjects is used to set the attribute to determine the maximum
+     * number of objects allowed in the memory cache. If the max number of
+     * objects or the cache size is set, the default for the one not set is
+     * ignored. If both are set, both are used to determine the capacity of the
+     * cache, i.e., object will be removed from the cache if either limit is
+     * reached. TODO: move to MemoryCache config file.
+     * <p>
+     * @param size
+     *            The new maxObjects value
+     */
+    void setMaxObjects( int size );
+
+    /**
+     * Gets the maxObjects attribute of the ICompositeCacheAttributes object
+     * <p>
+     * @return The maxObjects value
+     */
+    int getMaxObjects();
+
+    /**
+     * Sets the useDisk attribute of the ICompositeCacheAttributes object
+     * <p>
+     * @param useDisk
+     *            The new useDisk value
+     */
+    void setUseDisk( boolean useDisk );
+
+    /**
+     * Gets the useDisk attribute of the ICompositeCacheAttributes object
+     * <p>
+     * @return The useDisk value
+     */
+    boolean isUseDisk();
+
+    /**
+     * set whether the cache should use a lateral cache
+     * <p>
+     * @param d
+     *            The new useLateral value
+     */
+    void setUseLateral( boolean d );
+
+    /**
+     * Gets the useLateral attribute of the ICompositeCacheAttributes object
+     * <p>
+     * @return The useLateral value
+     */
+    boolean isUseLateral();
+
+    /**
+     * Sets whether the cache is remote enabled
+     * <p>
+     * @param isRemote
+     *            The new useRemote value
+     */
+    void setUseRemote( boolean isRemote );
+
+    /**
+     * returns whether the cache is remote enabled
+     * <p>
+     * @return The useRemote value
+     */
+    boolean isUseRemote();
+
+    /**
+     * Sets the name of the cache, referenced by the appropriate manager.
+     * <p>
+     * @param s
+     *            The new cacheName value
+     */
+    void setCacheName( String s );
+
+    /**
+     * Gets the cacheName attribute of the ICompositeCacheAttributes object
+     * <p>
+     * @return The cacheName value
+     */
+    String getCacheName();
+
+    /**
+     * Sets the name of the MemoryCache, referenced by the appropriate manager.
+     * TODO: create a separate memory cache attribute class.
+     * <p>
+     * @param s
+     *            The new memoryCacheName value
+     */
+    void setMemoryCacheName( String s );
+
+    /**
+     * Gets the memoryCacheName attribute of the ICompositeCacheAttributes
+     * object
+     * <p>
+     * @return The memoryCacheName value
+     */
+    String getMemoryCacheName();
+
+    /**
+     * Whether the memory cache should perform background memory shrinkage.
+     * <p>
+     * @param useShrinker
+     *            The new UseMemoryShrinker value
+     */
+    void setUseMemoryShrinker( boolean useShrinker );
+
+    /**
+     * Whether the memory cache should perform background memory shrinkage.
+     * <p>
+     * @return The UseMemoryShrinker value
+     */
+    boolean isUseMemoryShrinker();
+
+    /**
+     * If UseMemoryShrinker is true the memory cache should auto-expire elements
+     * to reclaim space.
+     * <p>
+     * @param seconds
+     *            The new MaxMemoryIdleTimeSeconds value
+     */
+    void setMaxMemoryIdleTimeSeconds( long seconds );
+
+    /**
+     * If UseMemoryShrinker is true the memory cache should auto-expire elements
+     * to reclaim space.
+     * <p>
+     * @return The MaxMemoryIdleTimeSeconds value
+     */
+    long getMaxMemoryIdleTimeSeconds();
+
+    /**
+     * If UseMemoryShrinker is true the memory cache should auto-expire elements
+     * to reclaim space. This sets the shrinker interval.
+     * <p>
+     * @param seconds
+     *            The new ShrinkerIntervalSeconds value
+     */
+    void setShrinkerIntervalSeconds( long seconds );
+
+    /**
+     * If UseMemoryShrinker is true the memory cache should auto-expire elements
+     * to reclaim space. This gets the shrinker interval.
+     * <p>
+     * @return The ShrinkerIntervalSeconds value
+     */
+    long getShrinkerIntervalSeconds();
+
+    /**
+     * If UseMemoryShrinker is true the memory cache should auto-expire elements
+     * to reclaim space. This sets the maximum number of items to spool per run.
+     * <p>
+     * @param maxSpoolPerRun
+     *            The new maxSpoolPerRun value
+     */
+    void setMaxSpoolPerRun( int maxSpoolPerRun );
+
+    /**
+     * If UseMemoryShrinker is true the memory cache should auto-expire elements
+     * to reclaim space. This gets the maximum number of items to spool per run.
+     * <p>
+     * @return The maxSpoolPerRun value
+     */
+    int getMaxSpoolPerRun();
+
+    /**
+     * By default this is SWAP_ONLY.
+     * <p>
+     * @param diskUsagePattern The diskUsagePattern to set.
+     */
+    void setDiskUsagePattern( DiskUsagePattern diskUsagePattern );
+
+    /**
+     * Translates the name to the disk usage pattern short value.
+     * <p>
+     * The allowed values are SWAP and UPDATE.
+     * <p>
+     * @param diskUsagePatternName The diskUsagePattern to set.
+     */
+    void setDiskUsagePatternName( String diskUsagePatternName );
+
+    /**
+     * @return Returns the diskUsagePattern.
+     */
+    DiskUsagePattern getDiskUsagePattern();
+
+    /**
+     * Number to send to disk at at time when memory is full.
+     * <p>
+     * @return int
+     */
+    int getSpoolChunkSize();
+
+    /**
+     * Number to send to disk at a time.
+     * <p>
+     * @param spoolChunkSize
+     */
+    void setSpoolChunkSize( int spoolChunkSize );
+
+    /**
+     * Clone object
+     */
+    ICompositeCacheAttributes clone();
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICompositeCacheManager.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICompositeCacheManager.java
new file mode 100644
index 0000000..d24d7a9
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/ICompositeCacheManager.java
@@ -0,0 +1,64 @@
+package org.apache.commons.jcs3.engine.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Properties;
+
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCache;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+
+/**
+ * I need the interface so I can plug in mock managers for testing.
+ *
+ * @author Aaron Smuts
+ */
+public interface ICompositeCacheManager extends IShutdownObservable
+{
+    /**
+     * Gets the cache attribute of the CacheHub object
+     *
+     * @param cacheName
+     * @return CompositeCache
+     */
+    <K, V> CompositeCache<K, V>  getCache( String cacheName );
+
+    /**
+     * Gets the auxiliary cache attribute of the CacheHub object
+     *
+     * @param auxName
+     * @param cacheName
+     * @return AuxiliaryCache
+     */
+    <K, V> AuxiliaryCache<K, V>  getAuxiliaryCache( String auxName, String cacheName );
+
+    /**
+     * This is exposed so other manager can get access to the props.
+     * <p>
+     * @return the configurationProperties
+     */
+    Properties getConfigurationProperties();
+
+    /**
+     * Gets stats for debugging.
+     * <p>
+     * @return String
+     */
+    String getStats();
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/IElementAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/IElementAttributes.java
new file mode 100644
index 0000000..718cc00
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/IElementAttributes.java
@@ -0,0 +1,204 @@
+package org.apache.commons.jcs3.engine.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.jcs3.engine.control.event.behavior.IElementEventHandler;
+
+/**
+ * Interface for cache element attributes classes. Every item is the cache is associated with an
+ * element attributes object. It is used to track the life of the object as well as to restrict its
+ * behavior. By default, elements get a clone of the region's attributes.
+ */
+public interface IElementAttributes extends Serializable, Cloneable
+{
+    /**
+     * Sets the maxLife attribute of the IAttributes object.
+     * <p>
+     * @param mls The new MaxLifeSeconds value
+     */
+    void setMaxLife(long mls);
+
+    /**
+     * Sets the maxLife attribute of the IAttributes object. How many seconds it can live after
+     * creation.
+     * <p>
+     * If this is exceeded the element will not be returned, instead it will be removed. It will be
+     * removed on retrieval, or removed actively if the memory shrinker is turned on.
+     * @return The MaxLifeSeconds value
+     */
+    long getMaxLife();
+
+    /**
+     * Sets the idleTime attribute of the IAttributes object. This is the maximum time the item can
+     * be idle in the cache, that is not accessed.
+     * <p>
+     * If this is exceeded the element will not be returned, instead it will be removed. It will be
+     * removed on retrieval, or removed actively if the memory shrinker is turned on.
+     * @param idle The new idleTime value
+     */
+    void setIdleTime( long idle );
+
+    /**
+     * Size in bytes. This is not used except in the admin pages. It will be 0 by default
+     * and is only updated when the element is serialized.
+     * <p>
+     * @param size The new size value
+     */
+    void setSize( int size );
+
+    /**
+     * Gets the size attribute of the IAttributes object
+     * <p>
+     * @return The size value
+     */
+    int getSize();
+
+    /**
+     * Gets the createTime attribute of the IAttributes object.
+     * <p>
+     * This should be the current time in milliseconds returned by the sysutem call when the element
+     * is put in the cache.
+     * <p>
+     * Putting an item in the cache overrides any existing items.
+     * @return The createTime value
+     */
+    long getCreateTime();
+
+    /**
+     * Gets the LastAccess attribute of the IAttributes object.
+     * <p>
+     * @return The LastAccess value.
+     */
+    long getLastAccessTime();
+
+    /**
+     * Sets the LastAccessTime as now of the IElementAttributes object
+     */
+    void setLastAccessTimeNow();
+
+    /**
+     * Gets the idleTime attribute of the IAttributes object
+     * @return The idleTime value
+     */
+    long getIdleTime();
+
+    /**
+     * Gets the time left to live of the IAttributes object.
+     * <p>
+     * This is the (max life + create time) - current time.
+     * @return The TimeToLiveSeconds value
+     */
+    long getTimeToLiveSeconds();
+
+    /**
+     * Can this item be spooled to disk
+     * <p>
+     * By default this is true.
+     * @return The spoolable value
+     */
+    boolean getIsSpool();
+
+    /**
+     * Sets the isSpool attribute of the IElementAttributes object
+     * <p>
+     * By default this is true.
+     * @param val The new isSpool value
+     */
+    void setIsSpool( boolean val );
+
+    /**
+     * Is this item laterally distributable. Can it be sent to auxiliaries of type lateral.
+     * <p>
+     * By default this is true.
+     * @return The isLateral value
+     */
+    boolean getIsLateral();
+
+    /**
+     * Sets the isLateral attribute of the IElementAttributes object
+     * <p>
+     * By default this is true.
+     * @param val The new isLateral value
+     */
+    void setIsLateral( boolean val );
+
+    /**
+     * Can this item be sent to the remote cache.
+     * <p>
+     * By default this is true.
+     * @return The isRemote value
+     */
+    boolean getIsRemote();
+
+    /**
+     * Sets the isRemote attribute of the IElementAttributes object.
+     * <p>
+     * By default this is true.
+     * @param val The new isRemote value
+     */
+    void setIsRemote( boolean val );
+
+    /**
+     * This turns off expiration if it is true.
+     * @return The IsEternal value
+     */
+    boolean getIsEternal();
+
+    /**
+     * Sets the isEternal attribute of the IElementAttributes object
+     * @param val The new isEternal value
+     */
+    void setIsEternal( boolean val );
+
+    /**
+     * Adds a ElementEventHandler. Handler's can be registered for multiple events. A registered
+     * handler will be called at every recognized event.
+     * @param eventHandler The feature to be added to the ElementEventHandler
+     */
+    void addElementEventHandler( IElementEventHandler eventHandler );
+
+    /**
+     * Gets the elementEventHandlers.
+     * <p>
+     * Event handlers are transient. The only events defined are in memory events. All handlers are
+     * lost if the item goes to disk.
+     * @return The elementEventHandlers value, null if there are none
+     */
+    ArrayList<IElementEventHandler> getElementEventHandlers();
+
+    /**
+     * Sets the eventHandlers of the IElementAttributes object
+     * @param eventHandlers value
+     */
+    void addElementEventHandlers( List<IElementEventHandler> eventHandlers );
+
+    long getTimeFactorForMilliseconds();
+
+    void setTimeFactorForMilliseconds(long factor);
+
+    /**
+     * Clone object
+     */
+    IElementAttributes clone();
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/IElementSerializer.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/IElementSerializer.java
new file mode 100644
index 0000000..ca91053
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/IElementSerializer.java
@@ -0,0 +1,51 @@
+package org.apache.commons.jcs3.engine.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+
+/**
+ * Defines the behavior for cache element serializers. This layer of abstraction allows us to plug
+ * in different serialization mechanisms, such as a compressing standard serializer.
+ * <p>
+ * @author Aaron Smuts
+ */
+public interface IElementSerializer
+{
+    /**
+     * Turns an object into a byte array.
+     * @param obj
+     * @return byte[]
+     * @throws IOException
+     */
+    <T> byte[] serialize( T obj )
+        throws IOException;
+
+    /**
+     * Turns a byte array into an object.
+     * @param bytes data bytes
+     * @param loader class loader to use
+     * @return Object
+     * @throws IOException
+     * @throws ClassNotFoundException thrown if we don't know the object.
+     */
+    <T> T deSerialize( byte[] bytes, ClassLoader loader )
+        throws IOException, ClassNotFoundException;
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/IProvideScheduler.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/IProvideScheduler.java
new file mode 100644
index 0000000..dc9907d
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/IProvideScheduler.java
@@ -0,0 +1,38 @@
+package org.apache.commons.jcs3.engine.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.concurrent.ScheduledExecutorService;
+
+
+/**
+ * Marker interface for providers of the central ScheduledExecutorService
+ * <p>
+ * @author Thomas Vandahl
+ *
+ */
+public interface IProvideScheduler
+{
+    /**
+     * Get an instance of a central ScheduledExecutorService
+     * @return the central scheduler
+     */
+    ScheduledExecutorService getScheduledExecutorService();
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/IRequireScheduler.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/IRequireScheduler.java
new file mode 100644
index 0000000..32a3906
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/IRequireScheduler.java
@@ -0,0 +1,39 @@
+package org.apache.commons.jcs3.engine.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.concurrent.ScheduledExecutorService;
+
+
+/**
+ * Marker interface to allow the injection of a central ScheduledExecutorService
+ * for all modules requiring scheduled background operations.
+ * <p>
+ * @author Thomas Vandahl
+ *
+ */
+public interface IRequireScheduler
+{
+    /**
+     * Inject an instance of a central ScheduledExecutorService
+     * @param scheduledExecutor
+     */
+    void setScheduledExecutorService( ScheduledExecutorService scheduledExecutor );
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/IShutdownObservable.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/IShutdownObservable.java
new file mode 100644
index 0000000..a2d28dc
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/IShutdownObservable.java
@@ -0,0 +1,55 @@
+package org.apache.commons.jcs3.engine.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * ShutdownObservers can observe ShutdownObservable objects.
+ * The CacheManager is the primary observable that this is intended for.
+ * <p>
+ * Most shutdown operations will occur outside this framework for now.  The initial
+ * goal is to allow background threads that are not reachable through any reference
+ * that the cache manager maintains to be killed on shutdown.
+ * <p>
+ * Perhaps the composite cache itself should be the observable object.
+ * It doesn't make much of a difference.  There are some problems with
+ * region by region shutdown.  Some auxiliaries are local.  They will
+ * need to track when every region has shutdown before doing things like
+ * closing the socket with a lateral.
+ * <p>
+ * @author Aaron Smuts
+ *
+ */
+public interface IShutdownObservable
+{
+
+    /**
+     * Registers an observer with the observable object.
+     * @param observer
+     */
+    void registerShutdownObserver( IShutdownObserver observer );
+
+    /**
+     * Deregisters the observer with the observable.
+     *
+     * @param observer
+     */
+    void deregisterShutdownObserver( IShutdownObserver observer );
+
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/IShutdownObserver.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/IShutdownObserver.java
new file mode 100644
index 0000000..eef30c6
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/IShutdownObserver.java
@@ -0,0 +1,41 @@
+package org.apache.commons.jcs3.engine.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * This interface is required of all shutdown observers.  These observers
+ * can observer ShutdownObservable objects.  The CacheManager is the primary
+ * observable that this is intended for.
+ * <p>
+ * Most shutdown operations will occur outside this framework for now.  The initial
+ * goal is to allow background threads that are not reachable through any reference
+ * that the cache manager maintains to be killed on shutdown.
+ *
+ * @author Aaron Smuts
+ *
+ */
+public interface IShutdownObserver
+{
+    /**
+     * Tells the observer that the observable has received a shutdown command.
+     *
+     */
+    void shutdown();
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/IZombie.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/IZombie.java
new file mode 100644
index 0000000..bd99756
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/IZombie.java
@@ -0,0 +1,30 @@
+package org.apache.commons.jcs3.engine.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Interface to mark an object as zombie for error recovery purposes.
+ *
+ */
+public interface IZombie
+{
+    // Zombies have no inner life.
+    // No qaulia found.
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/package.html b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/package.html
similarity index 100%
rename from commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/behavior/package.html
rename to commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/behavior/package.html
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/CompositeCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/CompositeCache.java
new file mode 100644
index 0000000..75e51a1
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/CompositeCache.java
@@ -0,0 +1,1693 @@
+package org.apache.commons.jcs3.engine.control;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.commons.jcs3.access.exception.CacheException;
+import org.apache.commons.jcs3.access.exception.ObjectNotFoundException;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCache;
+import org.apache.commons.jcs3.engine.CacheStatus;
+import org.apache.commons.jcs3.engine.behavior.ICache;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes;
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+import org.apache.commons.jcs3.engine.behavior.IRequireScheduler;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes.DiskUsagePattern;
+import org.apache.commons.jcs3.engine.control.event.ElementEvent;
+import org.apache.commons.jcs3.engine.control.event.behavior.ElementEventType;
+import org.apache.commons.jcs3.engine.control.event.behavior.IElementEvent;
+import org.apache.commons.jcs3.engine.control.event.behavior.IElementEventHandler;
+import org.apache.commons.jcs3.engine.control.event.behavior.IElementEventQueue;
+import org.apache.commons.jcs3.engine.control.group.GroupId;
+import org.apache.commons.jcs3.engine.match.KeyMatcherPatternImpl;
+import org.apache.commons.jcs3.engine.match.behavior.IKeyMatcher;
+import org.apache.commons.jcs3.engine.memory.behavior.IMemoryCache;
+import org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache;
+import org.apache.commons.jcs3.engine.memory.shrinking.ShrinkerThread;
+import org.apache.commons.jcs3.engine.stats.CacheStats;
+import org.apache.commons.jcs3.engine.stats.StatElement;
+import org.apache.commons.jcs3.engine.stats.behavior.ICacheStats;
+import org.apache.commons.jcs3.engine.stats.behavior.IStatElement;
+import org.apache.commons.jcs3.engine.stats.behavior.IStats;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * This is the primary hub for a single cache/region. It controls the flow of items through the
+ * cache. The auxiliary and memory caches are plugged in here.
+ * <p>
+ * This is the core of a JCS region. Hence, this simple class is the core of JCS.
+ */
+public class CompositeCache<K, V>
+    implements ICache<K, V>, IRequireScheduler
+{
+    /** log instance */
+    private static final Log log = LogManager.getLog(CompositeCache.class);
+
+    /**
+     * EventQueue for handling element events. Lazy initialized. One for each region. To be more efficient, the manager
+     * should pass a shared queue in.
+     */
+    private IElementEventQueue elementEventQ;
+
+    /** Auxiliary caches. */
+    @SuppressWarnings("unchecked") // OK because this is an empty array
+    private AuxiliaryCache<K, V>[] auxCaches = new AuxiliaryCache[0];
+
+    /** is this alive? */
+    private final AtomicBoolean alive;
+
+    /** Region Elemental Attributes, default. */
+    private IElementAttributes attr;
+
+    /** Cache Attributes, for hub and memory auxiliary. */
+    private ICompositeCacheAttributes cacheAttr;
+
+    /** How many times update was called. */
+    private final AtomicLong updateCount;
+
+    /** How many times remove was called. */
+    private final AtomicLong removeCount;
+
+    /** Memory cache hit count */
+    private final AtomicLong hitCountRam;
+
+    /** Auxiliary cache hit count (number of times found in ANY auxiliary) */
+    private final AtomicLong hitCountAux;
+
+    /** Count of misses where element was not found. */
+    private final AtomicLong missCountNotFound;
+
+    /** Count of misses where element was expired. */
+    private final AtomicLong missCountExpired;
+
+    /** Cache manager. */
+    private CompositeCacheManager cacheManager = null;
+
+    /**
+     * The cache hub can only have one memory cache. This could be made more flexible in the future,
+     * but they are tied closely together. More than one doesn't make much sense.
+     */
+    private IMemoryCache<K, V> memCache;
+
+    /** Key matcher used by the getMatching API */
+    private IKeyMatcher<K> keyMatcher = new KeyMatcherPatternImpl<>();
+
+    private ScheduledFuture<?> future;
+
+    /**
+     * Constructor for the Cache object
+     * <p>
+     * @param cattr The cache attribute
+     * @param attr The default element attributes
+     */
+    public CompositeCache(ICompositeCacheAttributes cattr, IElementAttributes attr)
+    {
+        this.attr = attr;
+        this.cacheAttr = cattr;
+        this.alive = new AtomicBoolean(true);
+        this.updateCount = new AtomicLong(0);
+        this.removeCount = new AtomicLong(0);
+        this.hitCountRam = new AtomicLong(0);
+        this.hitCountAux = new AtomicLong(0);
+        this.missCountNotFound = new AtomicLong(0);
+        this.missCountExpired = new AtomicLong(0);
+
+        createMemoryCache(cattr);
+
+        log.info("Constructed cache with name [{0}] and cache attributes {1}",
+                cacheAttr.getCacheName(), cattr);
+    }
+
+    /**
+     * Injector for Element event queue
+     *
+     * @param queue
+     */
+    public void setElementEventQueue(IElementEventQueue queue)
+    {
+        this.elementEventQ = queue;
+    }
+
+    /**
+     * Injector for cache manager
+     *
+     * @param manager
+     */
+    public void setCompositeCacheManager(CompositeCacheManager manager)
+    {
+        this.cacheManager = manager;
+    }
+
+    /**
+     * @see org.apache.commons.jcs3.engine.behavior.IRequireScheduler#setScheduledExecutorService(java.util.concurrent.ScheduledExecutorService)
+     */
+    @Override
+    public void setScheduledExecutorService(ScheduledExecutorService scheduledExecutor)
+    {
+        if (cacheAttr.isUseMemoryShrinker())
+        {
+            future = scheduledExecutor.scheduleAtFixedRate(
+                    new ShrinkerThread<>(this), 0, cacheAttr.getShrinkerIntervalSeconds(),
+                    TimeUnit.SECONDS);
+        }
+    }
+
+    /**
+     * This sets the list of auxiliary caches for this region.
+     * <p>
+     * @param auxCaches
+     */
+    public void setAuxCaches(AuxiliaryCache<K, V>[] auxCaches)
+    {
+        this.auxCaches = auxCaches;
+    }
+
+    /**
+     * Get the list of auxiliary caches for this region.
+     * <p>
+     * @return an array of auxiliary caches, may be empty, never null
+     */
+    public AuxiliaryCache<K, V>[] getAuxCaches()
+    {
+        return this.auxCaches;
+    }
+
+    /**
+     * Standard update method.
+     * <p>
+     * @param ce
+     * @throws IOException
+     */
+    @Override
+    public void update(ICacheElement<K, V> ce)
+        throws IOException
+    {
+        update(ce, false);
+    }
+
+    /**
+     * Standard update method.
+     * <p>
+     * @param ce
+     * @throws IOException
+     */
+    public void localUpdate(ICacheElement<K, V> ce)
+        throws IOException
+    {
+        update(ce, true);
+    }
+
+    /**
+     * Put an item into the cache. If it is localOnly, then do no notify remote or lateral
+     * auxiliaries.
+     * <p>
+     * @param cacheElement the ICacheElement&lt;K, V&gt;
+     * @param localOnly Whether the operation should be restricted to local auxiliaries.
+     * @throws IOException
+     */
+    protected void update(ICacheElement<K, V> cacheElement, boolean localOnly)
+        throws IOException
+    {
+
+        if (cacheElement.getKey() instanceof String
+            && cacheElement.getKey().toString().endsWith(NAME_COMPONENT_DELIMITER))
+        {
+            throw new IllegalArgumentException("key must not end with " + NAME_COMPONENT_DELIMITER
+                + " for a put operation");
+        }
+        else if (cacheElement.getKey() instanceof GroupId)
+        {
+            throw new IllegalArgumentException("key cannot be a GroupId " + " for a put operation");
+        }
+
+        log.debug("Updating memory cache {0}", () -> cacheElement.getKey());
+
+        updateCount.incrementAndGet();
+        memCache.update(cacheElement);
+        updateAuxiliaries(cacheElement, localOnly);
+
+        cacheElement.getElementAttributes().setLastAccessTimeNow();
+    }
+
+    /**
+     * This method is responsible for updating the auxiliaries if they are present. If it is local
+     * only, any lateral and remote auxiliaries will not be updated.
+     * <p>
+     * Before updating an auxiliary it checks to see if the element attributes permit the operation.
+     * <p>
+     * Disk auxiliaries are only updated if the disk cache is not merely used as a swap. If the disk
+     * cache is merely a swap, then items will only go to disk when they overflow from memory.
+     * <p>
+     * This is called by update(cacheElement, localOnly) after it updates the memory cache.
+     * <p>
+     * This is protected to make it testable.
+     * <p>
+     * @param cacheElement
+     * @param localOnly
+     * @throws IOException
+     */
+    protected void updateAuxiliaries(ICacheElement<K, V> cacheElement, boolean localOnly)
+        throws IOException
+    {
+        // UPDATE AUXILLIARY CACHES
+        // There are 3 types of auxiliary caches: remote, lateral, and disk
+        // more can be added if future auxiliary caches don't fit the model
+        // You could run a database cache as either a remote or a local disk.
+        // The types would describe the purpose.
+        if (auxCaches.length > 0)
+        {
+            log.debug("Updating auxiliary caches");
+        }
+        else
+        {
+            log.debug("No auxiliary cache to update");
+        }
+
+        for (ICache<K, V> aux : auxCaches)
+        {
+            if (aux == null)
+            {
+                continue;
+            }
+
+            log.debug("Auxiliary cache type: {0}", aux.getCacheType());
+
+            switch (aux.getCacheType())
+            {
+                // SEND TO REMOTE STORE
+                case REMOTE_CACHE:
+                    log.debug("ce.getElementAttributes().getIsRemote() = {0}",
+                        () -> cacheElement.getElementAttributes().getIsRemote());
+
+                    if (cacheElement.getElementAttributes().getIsRemote() && !localOnly)
+                    {
+                        try
+                        {
+                            // need to make sure the group cache understands that
+                            // the key is a group attribute on update
+                            aux.update(cacheElement);
+                            log.debug("Updated remote store for {0} {1}",
+                                    cacheElement.getKey(), cacheElement);
+                        }
+                        catch (IOException ex)
+                        {
+                            log.error("Failure in updateExclude", ex);
+                        }
+                    }
+                    break;
+
+                // SEND LATERALLY
+                case LATERAL_CACHE:
+                    // lateral can't do the checking since it is dependent on the
+                    // cache region restrictions
+                    log.debug("lateralcache in aux list: cattr {0}", () -> cacheAttr.isUseLateral());
+                    if (cacheAttr.isUseLateral() && cacheElement.getElementAttributes().getIsLateral() && !localOnly)
+                    {
+                        // DISTRIBUTE LATERALLY
+                        // Currently always multicast even if the value is
+                        // unchanged, to cause the cache item to move to the front.
+                        aux.update(cacheElement);
+                        log.debug("updated lateral cache for {0}", () -> cacheElement.getKey());
+                    }
+                    break;
+
+                // update disk if the usage pattern permits
+                case DISK_CACHE:
+                    log.debug("diskcache in aux list: cattr {0}", () -> cacheAttr.isUseDisk());
+                    if (cacheAttr.isUseDisk()
+                        && cacheAttr.getDiskUsagePattern() == DiskUsagePattern.UPDATE
+                        && cacheElement.getElementAttributes().getIsSpool())
+                    {
+                        aux.update(cacheElement);
+                        log.debug("updated disk cache for {0}", () -> cacheElement.getKey());
+                    }
+                    break;
+
+                default: // CACHE_HUB
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Writes the specified element to any disk auxiliaries. Might want to rename this "overflow" in
+     * case the hub wants to do something else.
+     * <p>
+     * If JCS is not configured to use the disk as a swap, that is if the the
+     * CompositeCacheAttribute diskUsagePattern is not SWAP_ONLY, then the item will not be spooled.
+     * <p>
+     * @param ce The CacheElement
+     */
+    public void spoolToDisk(ICacheElement<K, V> ce)
+    {
+        // if the item is not spoolable, return
+        if (!ce.getElementAttributes().getIsSpool())
+        {
+            // there is an event defined for this.
+            handleElementEvent(ce, ElementEventType.SPOOLED_NOT_ALLOWED);
+            return;
+        }
+
+        boolean diskAvailable = false;
+
+        // SPOOL TO DISK.
+        for (ICache<K, V> aux : auxCaches)
+        {
+            if (aux != null && aux.getCacheType() == CacheType.DISK_CACHE)
+            {
+                diskAvailable = true;
+
+                if (cacheAttr.getDiskUsagePattern() == DiskUsagePattern.SWAP)
+                {
+                    // write the last items to disk.2
+                    try
+                    {
+                        handleElementEvent(ce, ElementEventType.SPOOLED_DISK_AVAILABLE);
+                        aux.update(ce);
+                    }
+                    catch (IOException ex)
+                    {
+                        // impossible case.
+                        log.error("Problem spooling item to disk cache.", ex);
+                        throw new IllegalStateException(ex.getMessage());
+                    }
+
+                    log.debug("spoolToDisk done for: {0} on disk cache[{1}]",
+                            () -> ce.getKey(), () -> aux.getCacheName());
+                }
+                else
+                {
+                    log.debug("DiskCache available, but JCS is not configured "
+                            + "to use the DiskCache as a swap.");
+                }
+            }
+        }
+
+        if (!diskAvailable)
+        {
+            handleElementEvent(ce, ElementEventType.SPOOLED_DISK_NOT_AVAILABLE);
+        }
+    }
+
+    /**
+     * Gets an item from the cache.
+     * <p>
+     * @param key
+     * @return element from the cache, or null if not present
+     * @see org.apache.commons.jcs3.engine.behavior.ICache#get(Object)
+     */
+    @Override
+    public ICacheElement<K, V> get(K key)
+    {
+        return get(key, false);
+    }
+
+    /**
+     * Do not try to go remote or laterally for this get.
+     * <p>
+     * @param key
+     * @return ICacheElement
+     */
+    public ICacheElement<K, V> localGet(K key)
+    {
+        return get(key, true);
+    }
+
+    /**
+     * Look in memory, then disk, remote, or laterally for this item. The order is dependent on the
+     * order in the cache.ccf file.
+     * <p>
+     * Do not try to go remote or laterally for this get if it is localOnly. Otherwise try to go
+     * remote or lateral if such an auxiliary is configured for this region.
+     * <p>
+     * @param key
+     * @param localOnly
+     * @return ICacheElement
+     */
+    protected ICacheElement<K, V> get(K key, boolean localOnly)
+    {
+        ICacheElement<K, V> element = null;
+
+        boolean found = false;
+
+        log.debug("get: key = {0}, localOnly = {1}", key, localOnly);
+
+        try
+        {
+            // First look in memory cache
+            element = memCache.get(key);
+
+            if (element != null)
+            {
+                // Found in memory cache
+                if (isExpired(element))
+                {
+                    log.debug("{0} - Memory cache hit, but element expired",
+                            () -> cacheAttr.getCacheName());
+
+                    doExpires(element);
+                    element = null;
+                }
+                else
+                {
+                    log.debug("{0} - Memory cache hit", () -> cacheAttr.getCacheName());
+
+                    // Update counters
+                    hitCountRam.incrementAndGet();
+                }
+
+                found = true;
+            }
+            else
+            {
+                // Item not found in memory. If local invocation look in aux
+                // caches, even if not local look in disk auxiliaries
+                for (AuxiliaryCache<K, V> aux : auxCaches)
+                {
+                    if (aux != null)
+                    {
+                        CacheType cacheType = aux.getCacheType();
+
+                        if (!localOnly || cacheType == CacheType.DISK_CACHE)
+                        {
+                            log.debug("Attempting to get from aux [{0}] which is of type: {1}",
+                                    () -> aux.getCacheName(), () -> cacheType);
+
+                            try
+                            {
+                                element = aux.get(key);
+                            }
+                            catch (IOException e)
+                            {
+                                log.error("Error getting from aux", e);
+                            }
+                        }
+
+                        log.debug("Got CacheElement: {0}", element);
+
+                        // Item found in one of the auxiliary caches.
+                        if (element != null)
+                        {
+                            if (isExpired(element))
+                            {
+                                log.debug("{0} - Aux cache[{1}] hit, but element expired.",
+                                        () -> cacheAttr.getCacheName(), () -> aux.getCacheName());
+
+                                // This will tell the remotes to remove the item
+                                // based on the element's expiration policy. The elements attributes
+                                // associated with the item when it created govern its behavior
+                                // everywhere.
+                                doExpires(element);
+                                element = null;
+                            }
+                            else
+                            {
+                                log.debug("{0} - Aux cache[{1}] hit.",
+                                        () -> cacheAttr.getCacheName(), () -> aux.getCacheName());
+
+                                // Update counters
+                                hitCountAux.incrementAndGet();
+                                copyAuxiliaryRetrievedItemToMemory(element);
+                            }
+
+                            found = true;
+
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+        catch (IOException e)
+        {
+            log.error("Problem encountered getting element.", e);
+        }
+
+        if (!found)
+        {
+            missCountNotFound.incrementAndGet();
+
+            log.debug("{0} - Miss", () -> cacheAttr.getCacheName());
+        }
+
+        if (element != null)
+        {
+            element.getElementAttributes().setLastAccessTimeNow();
+        }
+
+        return element;
+    }
+
+    protected void doExpires(ICacheElement<K, V> element)
+    {
+        missCountExpired.incrementAndGet();
+        remove(element.getKey());
+    }
+
+    /**
+     * Gets multiple items from the cache based on the given set of keys.
+     * <p>
+     * @param keys
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache for any of these keys
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMultiple(Set<K> keys)
+    {
+        return getMultiple(keys, false);
+    }
+
+    /**
+     * Gets multiple items from the cache based on the given set of keys. Do not try to go remote or
+     * laterally for this data.
+     * <p>
+     * @param keys
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache for any of these keys
+     */
+    public Map<K, ICacheElement<K, V>> localGetMultiple(Set<K> keys)
+    {
+        return getMultiple(keys, true);
+    }
+
+    /**
+     * Look in memory, then disk, remote, or laterally for these items. The order is dependent on
+     * the order in the cache.ccf file. Keep looking in each cache location until either the element
+     * is found, or the method runs out of places to look.
+     * <p>
+     * Do not try to go remote or laterally for this get if it is localOnly. Otherwise try to go
+     * remote or lateral if such an auxiliary is configured for this region.
+     * <p>
+     * @param keys
+     * @param localOnly
+     * @return ICacheElement
+     */
+    protected Map<K, ICacheElement<K, V>> getMultiple(Set<K> keys, boolean localOnly)
+    {
+        Map<K, ICacheElement<K, V>> elements = new HashMap<>();
+
+        log.debug("get: key = {0}, localOnly = {1}", keys, localOnly);
+
+        try
+        {
+            // First look in memory cache
+            elements.putAll(getMultipleFromMemory(keys));
+
+            // If fewer than all items were found in memory, then keep looking.
+            if (elements.size() != keys.size())
+            {
+                Set<K> remainingKeys = pruneKeysFound(keys, elements);
+                elements.putAll(getMultipleFromAuxiliaryCaches(remainingKeys, localOnly));
+            }
+        }
+        catch (IOException e)
+        {
+            log.error("Problem encountered getting elements.", e);
+        }
+
+        // if we didn't find all the elements, increment the miss count by the number of elements not found
+        if (elements.size() != keys.size())
+        {
+            missCountNotFound.addAndGet(keys.size() - elements.size());
+
+            log.debug("{0} - {1} Misses", () -> cacheAttr.getCacheName(),
+                    () -> keys.size() - elements.size());
+        }
+
+        return elements;
+    }
+
+    /**
+     * Gets items for the keys in the set. Returns a map: key -> result.
+     * <p>
+     * @param keys
+     * @return the elements found in the memory cache
+     * @throws IOException
+     */
+    private Map<K, ICacheElement<K, V>> getMultipleFromMemory(Set<K> keys)
+        throws IOException
+    {
+        Map<K, ICacheElement<K, V>> elementsFromMemory = memCache.getMultiple(keys);
+        elementsFromMemory.entrySet().removeIf(entry -> {
+            ICacheElement<K, V> element = entry.getValue();
+            if (isExpired(element))
+            {
+                log.debug("{0} - Memory cache hit, but element expired",
+                        () -> cacheAttr.getCacheName());
+
+                doExpires(element);
+                return true;
+            }
+            else
+            {
+                log.debug("{0} - Memory cache hit", () -> cacheAttr.getCacheName());
+
+                // Update counters
+                hitCountRam.incrementAndGet();
+                return false;
+            }
+        });
+
+        return elementsFromMemory;
+    }
+
+    /**
+     * If local invocation look in aux caches, even if not local look in disk auxiliaries.
+     * <p>
+     * @param keys
+     * @param localOnly
+     * @return the elements found in the auxiliary caches
+     * @throws IOException
+     */
+    private Map<K, ICacheElement<K, V>> getMultipleFromAuxiliaryCaches(Set<K> keys, boolean localOnly)
+        throws IOException
+    {
+        Map<K, ICacheElement<K, V>> elements = new HashMap<>();
+        Set<K> remainingKeys = new HashSet<>(keys);
+
+        for (AuxiliaryCache<K, V> aux : auxCaches)
+        {
+            if (aux != null)
+            {
+                Map<K, ICacheElement<K, V>> elementsFromAuxiliary =
+                    new HashMap<>();
+
+                CacheType cacheType = aux.getCacheType();
+
+                if (!localOnly || cacheType == CacheType.DISK_CACHE)
+                {
+                    log.debug("Attempting to get from aux [{0}] which is of type: {1}",
+                            () -> aux.getCacheName(), () -> cacheType);
+
+                    try
+                    {
+                        elementsFromAuxiliary.putAll(aux.getMultiple(remainingKeys));
+                    }
+                    catch (IOException e)
+                    {
+                        log.error("Error getting from aux", e);
+                    }
+                }
+
+                log.debug("Got CacheElements: {0}", elementsFromAuxiliary);
+
+                processRetrievedElements(aux, elementsFromAuxiliary);
+                elements.putAll(elementsFromAuxiliary);
+
+                if (elements.size() == keys.size())
+                {
+                    break;
+                }
+                else
+                {
+                    remainingKeys = pruneKeysFound(keys, elements);
+                }
+            }
+        }
+
+        return elements;
+    }
+
+    /**
+     * Build a map of all the matching elements in all of the auxiliaries and memory.
+     * <p>
+     * @param pattern
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache for any matching keys
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMatching(String pattern)
+    {
+        return getMatching(pattern, false);
+    }
+
+    /**
+     * Build a map of all the matching elements in all of the auxiliaries and memory. Do not try to
+     * go remote or laterally for this data.
+     * <p>
+     * @param pattern
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache for any matching keys
+     */
+    public Map<K, ICacheElement<K, V>> localGetMatching(String pattern)
+    {
+        return getMatching(pattern, true);
+    }
+
+    /**
+     * Build a map of all the matching elements in all of the auxiliaries and memory. Items in
+     * memory will replace from the auxiliaries in the returned map. The auxiliaries are accessed in
+     * opposite order. It's assumed that those closer to home are better.
+     * <p>
+     * Do not try to go remote or laterally for this get if it is localOnly. Otherwise try to go
+     * remote or lateral if such an auxiliary is configured for this region.
+     * <p>
+     * @param pattern
+     * @param localOnly
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache for any matching keys
+     */
+    protected Map<K, ICacheElement<K, V>> getMatching(String pattern, boolean localOnly)
+    {
+        log.debug("get: pattern [{0}], localOnly = {1}", pattern, localOnly);
+
+        try
+        {
+            return Stream.concat(
+                    getMatchingFromMemory(pattern).entrySet().stream(),
+                    getMatchingFromAuxiliaryCaches(pattern, localOnly).entrySet().stream())
+                    .collect(Collectors.toMap(
+                            entry -> entry.getKey(),
+                            entry -> entry.getValue(),
+                            // Prefer memory entries
+                            (mem, aux) -> mem));
+        }
+        catch (IOException e)
+        {
+            log.error("Problem encountered getting elements.", e);
+        }
+
+        return new HashMap<>();
+    }
+
+    /**
+     * Gets the key array from the memcache. Builds a set of matches. Calls getMultiple with the
+     * set. Returns a map: key -&gt; result.
+     * <p>
+     * @param pattern
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache for any matching keys
+     * @throws IOException
+     */
+    protected Map<K, ICacheElement<K, V>> getMatchingFromMemory(String pattern)
+        throws IOException
+    {
+        // find matches in key array
+        // this avoids locking the memory cache, but it uses more memory
+        Set<K> keyArray = memCache.getKeySet();
+        Set<K> matchingKeys = getKeyMatcher().getMatchingKeysFromArray(pattern, keyArray);
+
+        // call get multiple
+        return getMultipleFromMemory(matchingKeys);
+    }
+
+    /**
+     * If local invocation look in aux caches, even if not local look in disk auxiliaries.
+     * <p>
+     * Moves in reverse order of definition. This will allow you to override those that are from the
+     * remote with those on disk.
+     * <p>
+     * @param pattern
+     * @param localOnly
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache for any matching keys
+     * @throws IOException
+     */
+    private Map<K, ICacheElement<K, V>> getMatchingFromAuxiliaryCaches(String pattern, boolean localOnly)
+        throws IOException
+    {
+        Map<K, ICacheElement<K, V>> elements = new HashMap<>();
+
+        for (int i = auxCaches.length - 1; i >= 0; i--)
+        {
+            AuxiliaryCache<K, V> aux = auxCaches[i];
+
+            if (aux != null)
+            {
+                Map<K, ICacheElement<K, V>> elementsFromAuxiliary =
+                    new HashMap<>();
+
+                CacheType cacheType = aux.getCacheType();
+
+                if (!localOnly || cacheType == CacheType.DISK_CACHE)
+                {
+                    log.debug("Attempting to get from aux [{0}] which is of type: {1}",
+                            () -> aux.getCacheName(), () -> cacheType);
+
+                    try
+                    {
+                        elementsFromAuxiliary.putAll(aux.getMatching(pattern));
+                    }
+                    catch (IOException e)
+                    {
+                        log.error("Error getting from aux", e);
+                    }
+
+                    log.debug("Got CacheElements: {0}", elementsFromAuxiliary);
+
+                    processRetrievedElements(aux, elementsFromAuxiliary);
+                    elements.putAll(elementsFromAuxiliary);
+                }
+            }
+        }
+
+        return elements;
+    }
+
+    /**
+     * Remove expired elements retrieved from an auxiliary. Update memory with good items.
+     * <p>
+     * @param aux the auxiliary cache instance
+     * @param elementsFromAuxiliary
+     * @throws IOException
+     */
+    private void processRetrievedElements(AuxiliaryCache<K, V> aux, Map<K, ICacheElement<K, V>> elementsFromAuxiliary)
+        throws IOException
+    {
+        elementsFromAuxiliary.entrySet().removeIf(entry -> {
+            ICacheElement<K, V> element = entry.getValue();
+
+            // Item found in one of the auxiliary caches.
+            if (element != null)
+            {
+                if (isExpired(element))
+                {
+                    log.debug("{0} - Aux cache[{1}] hit, but element expired.",
+                            () -> cacheAttr.getCacheName(), () -> aux.getCacheName());
+
+                    // This will tell the remote caches to remove the item
+                    // based on the element's expiration policy. The elements attributes
+                    // associated with the item when it created govern its behavior
+                    // everywhere.
+                    doExpires(element);
+                    return true;
+                }
+                else
+                {
+                    log.debug("{0} - Aux cache[{1}] hit.",
+                            () -> cacheAttr.getCacheName(), () -> aux.getCacheName());
+
+                    // Update counters
+                    hitCountAux.incrementAndGet();
+                    try
+                    {
+                        copyAuxiliaryRetrievedItemToMemory(element);
+                    }
+                    catch (IOException e)
+                    {
+                        log.error("{0} failed to copy element to memory {1}",
+                                cacheAttr.getCacheName(), element, e);
+                    }
+                }
+            }
+
+            return false;
+        });
+    }
+
+    /**
+     * Copies the item to memory if the memory size is greater than 0. Only spool if the memory
+     * cache size is greater than 0, else the item will immediately get put into purgatory.
+     * <p>
+     * @param element
+     * @throws IOException
+     */
+    private void copyAuxiliaryRetrievedItemToMemory(ICacheElement<K, V> element)
+        throws IOException
+    {
+        if (memCache.getCacheAttributes().getMaxObjects() > 0)
+        {
+            memCache.update(element);
+        }
+        else
+        {
+            log.debug("Skipping memory update since no items are allowed in memory");
+        }
+    }
+
+    /**
+     * Returns a set of keys that were not found.
+     * <p>
+     * @param keys
+     * @param foundElements
+     * @return the original set of cache keys, minus any cache keys present in the map keys of the
+     *         foundElements map
+     */
+    private Set<K> pruneKeysFound(Set<K> keys, Map<K, ICacheElement<K, V>> foundElements)
+    {
+        Set<K> remainingKeys = new HashSet<>(keys);
+        remainingKeys.removeAll(foundElements.keySet());
+
+        return remainingKeys;
+    }
+
+    /**
+     * Get a set of the keys for all elements in the cache
+     * <p>
+     * @return A set of the key type
+     */
+    public Set<K> getKeySet()
+    {
+        return getKeySet(false);
+    }
+
+    /**
+     * Get a set of the keys for all elements in the cache
+     * <p>
+     * @param localOnly true if only memory keys are requested
+     *
+     * @return A set of the key type
+     */
+    public Set<K> getKeySet(boolean localOnly)
+    {
+        HashSet<K> allKeys = new HashSet<>();
+
+        allKeys.addAll(memCache.getKeySet());
+        for (AuxiliaryCache<K, V> aux : auxCaches)
+        {
+            if (aux != null)
+            {
+                if(!localOnly || aux.getCacheType() == CacheType.DISK_CACHE)
+                {
+                    try
+                    {
+                        allKeys.addAll(aux.getKeySet());
+                    }
+                    catch (IOException e)
+                    {
+                        // ignore
+                    }
+                }
+            }
+        }
+        return allKeys;
+    }
+
+    /**
+     * Removes an item from the cache.
+     * <p>
+     * @param key
+     * @return true is it was removed
+     * @see org.apache.commons.jcs3.engine.behavior.ICache#remove(Object)
+     */
+    @Override
+    public boolean remove(K key)
+    {
+        return remove(key, false);
+    }
+
+    /**
+     * Do not propagate removeall laterally or remotely.
+     * <p>
+     * @param key
+     * @return true if the item was already in the cache.
+     */
+    public boolean localRemove(K key)
+    {
+        return remove(key, true);
+    }
+
+    /**
+     * fromRemote: If a remove call was made on a cache with both, then the remote should have been
+     * called. If it wasn't then the remote is down. we'll assume it is down for all. If it did come
+     * from the remote then the cache is remotely configured and lateral removal is unnecessary. If
+     * it came laterally then lateral removal is unnecessary. Does this assume that there is only
+     * one lateral and remote for the cache? Not really, the initial removal should take care of the
+     * problem if the source cache was similarly configured. Otherwise the remote cache, if it had
+     * no laterals, would remove all the elements from remotely configured caches, but if those
+     * caches had some other weird laterals that were not remotely configured, only laterally
+     * propagated then they would go out of synch. The same could happen for multiple remotes. If
+     * this looks necessary we will need to build in an identifier to specify the source of a
+     * removal.
+     * <p>
+     * @param key
+     * @param localOnly
+     * @return true if the item was in the cache, else false
+     */
+    protected boolean remove(K key, boolean localOnly)
+    {
+        removeCount.incrementAndGet();
+
+        boolean removed = false;
+
+        try
+        {
+            removed = memCache.remove(key);
+        }
+        catch (IOException e)
+        {
+            log.error(e);
+        }
+
+        // Removes from all auxiliary caches.
+        for (ICache<K, V> aux : auxCaches)
+        {
+            if (aux == null)
+            {
+                continue;
+            }
+
+            CacheType cacheType = aux.getCacheType();
+
+            // for now let laterals call remote remove but not vice versa
+            if (localOnly && (cacheType == CacheType.REMOTE_CACHE || cacheType == CacheType.LATERAL_CACHE))
+            {
+                continue;
+            }
+            try
+            {
+                log.debug("Removing {0} from cacheType {1}", key, cacheType);
+
+                boolean b = aux.remove(key);
+
+                // Don't take the remote removal into account.
+                if (!removed && cacheType != CacheType.REMOTE_CACHE)
+                {
+                    removed = b;
+                }
+            }
+            catch (IOException ex)
+            {
+                log.error("Failure removing from aux", ex);
+            }
+        }
+
+        return removed;
+    }
+
+    /**
+     * Clears the region. This command will be sent to all auxiliaries. Some auxiliaries, such as
+     * the JDBC disk cache, can be configured to not honor removeAll requests.
+     * <p>
+     * @see org.apache.commons.jcs3.engine.behavior.ICache#removeAll()
+     */
+    @Override
+    public void removeAll()
+        throws IOException
+    {
+        removeAll(false);
+    }
+
+    /**
+     * Will not pass the remove message remotely.
+     * <p>
+     * @throws IOException
+     */
+    public void localRemoveAll()
+        throws IOException
+    {
+        removeAll(true);
+    }
+
+    /**
+     * Removes all cached items.
+     * <p>
+     * @param localOnly must pass in false to get remote and lateral aux's updated. This prevents
+     *            looping.
+     * @throws IOException
+     */
+    protected void removeAll(boolean localOnly)
+        throws IOException
+    {
+        try
+        {
+            memCache.removeAll();
+
+            log.debug("Removed All keys from the memory cache.");
+        }
+        catch (IOException ex)
+        {
+            log.error("Trouble updating memory cache.", ex);
+        }
+
+        // Removes from all auxiliary disk caches.
+        for (ICache<K, V> aux : auxCaches)
+        {
+            if (aux != null && (aux.getCacheType() == CacheType.DISK_CACHE || !localOnly))
+            {
+                try
+                {
+                    log.debug("Removing All keys from cacheType {0}",
+                            () -> aux.getCacheType());
+
+                    aux.removeAll();
+                }
+                catch (IOException ex)
+                {
+                    log.error("Failure removing all from aux", ex);
+                }
+            }
+        }
+    }
+
+    /**
+     * Flushes all cache items from memory to auxiliary caches and close the auxiliary caches.
+     */
+    @Override
+    public void dispose()
+    {
+        dispose(false);
+    }
+
+    /**
+     * Invoked only by CacheManager. This method disposes of the auxiliaries one by one. For the
+     * disk cache, the items in memory are freed, meaning that they will be sent through the
+     * overflow channel to disk. After the auxiliaries are disposed, the memory cache is disposed.
+     * <p>
+     * @param fromRemote
+     */
+    public void dispose(boolean fromRemote)
+    {
+         // If already disposed, return immediately
+        if (alive.compareAndSet(true, false) == false)
+        {
+            return;
+        }
+
+        log.info("In DISPOSE, [{0}] fromRemote [{1}]",
+                () -> this.cacheAttr.getCacheName(), () -> fromRemote);
+
+        // Remove us from the cache managers list
+        // This will call us back but exit immediately
+        if (cacheManager != null)
+        {
+            cacheManager.freeCache(getCacheName(), fromRemote);
+        }
+
+        // Try to stop shrinker thread
+        if (future != null)
+        {
+            future.cancel(true);
+        }
+
+        // Now, shut down the event queue
+        if (elementEventQ != null)
+        {
+            elementEventQ.dispose();
+            elementEventQ = null;
+        }
+
+        // Dispose of each auxiliary cache, Remote auxiliaries will be
+        // skipped if 'fromRemote' is true.
+        for (ICache<K, V> aux : auxCaches)
+        {
+            try
+            {
+                // Skip this auxiliary if:
+                // - The auxiliary is null
+                // - The auxiliary is not alive
+                // - The auxiliary is remote and the invocation was remote
+                if (aux == null || aux.getStatus() != CacheStatus.ALIVE
+                    || (fromRemote && aux.getCacheType() == CacheType.REMOTE_CACHE))
+                {
+                    log.info("In DISPOSE, [{0}] SKIPPING auxiliary [{1}] fromRemote [{2}]",
+                            () -> this.cacheAttr.getCacheName(), () -> aux.getCacheName(),
+                            () -> fromRemote);
+                    continue;
+                }
+
+                log.info("In DISPOSE, [{0}] auxiliary [{1}]",
+                        () -> this.cacheAttr.getCacheName(), () -> aux.getCacheName());
+
+                // IT USED TO BE THE CASE THAT (If the auxiliary is not a lateral, or the cache
+                // attributes
+                // have 'getUseLateral' set, all the elements currently in
+                // memory are written to the lateral before disposing)
+                // I changed this. It was excessive. Only the disk cache needs the items, since only
+                // the disk cache is in a situation to not get items on a put.
+                if (aux.getCacheType() == CacheType.DISK_CACHE)
+                {
+                    int numToFree = memCache.getSize();
+                    memCache.freeElements(numToFree);
+
+                    log.info("In DISPOSE, [{0}] put {1} into auxiliary [{2}]",
+                            () -> this.cacheAttr.getCacheName(), () -> numToFree,
+                            () -> aux.getCacheName());
+                }
+
+                // Dispose of the auxiliary
+                aux.dispose();
+            }
+            catch (IOException ex)
+            {
+                log.error("Failure disposing of aux.", ex);
+            }
+        }
+
+        log.info("In DISPOSE, [{0}] disposing of memory cache.",
+                () -> this.cacheAttr.getCacheName());
+        try
+        {
+            memCache.dispose();
+        }
+        catch (IOException ex)
+        {
+            log.error("Failure disposing of memCache", ex);
+        }
+    }
+
+    /**
+     * Calling save cause the entire contents of the memory cache to be flushed to all auxiliaries.
+     * Though this put is extremely fast, this could bog the cache and should be avoided. The
+     * dispose method should call a version of this. Good for testing.
+     */
+    public void save()
+    {
+        if (alive.compareAndSet(true, false) == false)
+        {
+            return;
+        }
+
+        for (ICache<K, V> aux : auxCaches)
+        {
+            try
+            {
+                if (aux.getStatus() == CacheStatus.ALIVE)
+                {
+                    for (K key : memCache.getKeySet())
+                    {
+                        ICacheElement<K, V> ce = memCache.get(key);
+
+                        if (ce != null)
+                        {
+                            aux.update(ce);
+                        }
+                    }
+                }
+            }
+            catch (IOException ex)
+            {
+                log.error("Failure saving aux caches.", ex);
+            }
+        }
+
+        log.debug("Called save for [{0}]", () -> cacheAttr.getCacheName());
+    }
+
+    /**
+     * Gets the size attribute of the Cache object. This return the number of elements, not the byte
+     * size.
+     * <p>
+     * @return The size value
+     */
+    @Override
+    public int getSize()
+    {
+        return memCache.getSize();
+    }
+
+    /**
+     * Gets the cacheType attribute of the Cache object.
+     * <p>
+     * @return The cacheType value
+     */
+    @Override
+    public CacheType getCacheType()
+    {
+        return CacheType.CACHE_HUB;
+    }
+
+    /**
+     * Gets the status attribute of the Cache object.
+     * <p>
+     * @return The status value
+     */
+    @Override
+    public CacheStatus getStatus()
+    {
+        return alive.get() ? CacheStatus.ALIVE : CacheStatus.DISPOSED;
+    }
+
+    /**
+     * Gets stats for debugging.
+     * <p>
+     * @return String
+     */
+    @Override
+    public String getStats()
+    {
+        return getStatistics().toString();
+    }
+
+    /**
+     * This returns data gathered for this region and all the auxiliaries it currently uses.
+     * <p>
+     * @return Statistics and Info on the Region.
+     */
+    public ICacheStats getStatistics()
+    {
+        ICacheStats stats = new CacheStats();
+        stats.setRegionName(this.getCacheName());
+
+        // store the composite cache stats first
+        ArrayList<IStatElement<?>> elems = new ArrayList<>();
+
+        elems.add(new StatElement<>("HitCountRam", Long.valueOf(getHitCountRam())));
+        elems.add(new StatElement<>("HitCountAux", Long.valueOf(getHitCountAux())));
+
+        stats.setStatElements(elems);
+
+        // memory + aux, memory is not considered an auxiliary internally
+        int total = auxCaches.length + 1;
+        ArrayList<IStats> auxStats = new ArrayList<>(total);
+
+        auxStats.add(getMemoryCache().getStatistics());
+
+        for (AuxiliaryCache<K, V> aux : auxCaches)
+        {
+            auxStats.add(aux.getStatistics());
+        }
+
+        // store the auxiliary stats
+        stats.setAuxiliaryCacheStats(auxStats);
+
+        return stats;
+    }
+
+    /**
+     * Gets the cacheName attribute of the Cache object. This is also known as the region name.
+     * <p>
+     * @return The cacheName value
+     */
+    @Override
+    public String getCacheName()
+    {
+        return cacheAttr.getCacheName();
+    }
+
+    /**
+     * Gets the default element attribute of the Cache object This returns a copy. It does not
+     * return a reference to the attributes.
+     * <p>
+     * @return The attributes value
+     */
+    public IElementAttributes getElementAttributes()
+    {
+        if (attr != null)
+        {
+            return attr.clone();
+        }
+        return null;
+    }
+
+    /**
+     * Sets the default element attribute of the Cache object.
+     * <p>
+     * @param attr
+     */
+    public void setElementAttributes(IElementAttributes attr)
+    {
+        this.attr = attr;
+    }
+
+    /**
+     * Gets the ICompositeCacheAttributes attribute of the Cache object.
+     * <p>
+     * @return The ICompositeCacheAttributes value
+     */
+    public ICompositeCacheAttributes getCacheAttributes()
+    {
+        return this.cacheAttr;
+    }
+
+    /**
+     * Sets the ICompositeCacheAttributes attribute of the Cache object.
+     * <p>
+     * @param cattr The new ICompositeCacheAttributes value
+     */
+    public void setCacheAttributes(ICompositeCacheAttributes cattr)
+    {
+        this.cacheAttr = cattr;
+        // need a better way to do this, what if it is in error
+        this.memCache.initialize(this);
+    }
+
+    /**
+     * Gets the elementAttributes attribute of the Cache object.
+     * <p>
+     * @param key
+     * @return The elementAttributes value
+     * @throws CacheException
+     * @throws IOException
+     */
+    public IElementAttributes getElementAttributes(K key)
+        throws CacheException, IOException
+    {
+        ICacheElement<K, V> ce = get(key);
+        if (ce == null)
+        {
+            throw new ObjectNotFoundException("key " + key + " is not found");
+        }
+        return ce.getElementAttributes();
+    }
+
+    /**
+     * Determine if the element is expired based on the values of the element attributes
+     *
+     * @param element the element
+     *
+     * @return true if the element is expired
+     */
+    public boolean isExpired(ICacheElement<K, V> element)
+    {
+        return isExpired(element, System.currentTimeMillis(),
+                ElementEventType.EXCEEDED_MAXLIFE_ONREQUEST,
+                ElementEventType.EXCEEDED_IDLETIME_ONREQUEST);
+    }
+
+    /**
+     * Check if the element is expired based on the values of the element attributes
+     *
+     * @param element the element
+     * @param timestamp the timestamp to compare to
+     * @param eventMaxlife the event to fire in case the max life time is exceeded
+     * @param eventIdle the event to fire in case the idle time is exceeded
+     *
+     * @return true if the element is expired
+     */
+    public boolean isExpired(ICacheElement<K, V> element, long timestamp,
+            ElementEventType eventMaxlife, ElementEventType eventIdle)
+    {
+        try
+        {
+            IElementAttributes attributes = element.getElementAttributes();
+
+            if (!attributes.getIsEternal())
+            {
+                // Remove if maxLifeSeconds exceeded
+                long maxLifeSeconds = attributes.getMaxLife();
+                long createTime = attributes.getCreateTime();
+
+                final long timeFactorForMilliseconds = attributes.getTimeFactorForMilliseconds();
+
+                if (maxLifeSeconds != -1 && (timestamp - createTime) > (maxLifeSeconds * timeFactorForMilliseconds))
+                {
+                    log.debug("Exceeded maxLife: {0}", () -> element.getKey());
+
+                    handleElementEvent(element, eventMaxlife);
+                    return true;
+                }
+                long idleTime = attributes.getIdleTime();
+                long lastAccessTime = attributes.getLastAccessTime();
+
+                // Remove if maxIdleTime exceeded
+                // If you have a 0 size memory cache, then the last access will
+                // not get updated.
+                // you will need to set the idle time to -1.
+                if ((idleTime != -1) && (timestamp - lastAccessTime) > idleTime * timeFactorForMilliseconds)
+                {
+                    log.debug("Exceeded maxIdle: {0}", () -> element.getKey());
+
+                    handleElementEvent(element, eventIdle);
+                    return true;
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            log.error("Error determining expiration period, expiring", e);
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * If there are event handlers for the item, then create an event and queue it up.
+     * <p>
+     * This does not call handle directly; instead the handler and the event are put into a queue.
+     * This prevents the event handling from blocking normal cache operations.
+     * <p>
+     * @param element the item
+     * @param eventType the event type
+     */
+    public void handleElementEvent(ICacheElement<K, V> element, ElementEventType eventType)
+    {
+        ArrayList<IElementEventHandler> eventHandlers = element.getElementAttributes().getElementEventHandlers();
+        if (eventHandlers != null)
+        {
+            log.debug("Element Handlers are registered.  Create event type {0}", eventType);
+            if (elementEventQ == null)
+            {
+                log.warn("No element event queue available for cache {0}", getCacheName());
+                return;
+            }
+            IElementEvent<ICacheElement<K, V>> event = new ElementEvent<>(element, eventType);
+            for (IElementEventHandler hand : eventHandlers)
+            {
+                try
+                {
+                   elementEventQ.addElementEvent(hand, event);
+                }
+                catch (IOException e)
+                {
+                    log.error("Trouble adding element event to queue", e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Create the MemoryCache based on the config parameters.
+     * TODO: consider making this an auxiliary, despite its close tie to the CacheHub.
+     * TODO: might want to create a memory cache config file separate from that of the hub -- ICompositeCacheAttributes
+     * <p>
+     * @param cattr
+     */
+    private void createMemoryCache(ICompositeCacheAttributes cattr)
+    {
+        if (memCache == null)
+        {
+            try
+            {
+                Class<?> c = Class.forName(cattr.getMemoryCacheName());
+                @SuppressWarnings("unchecked") // Need cast
+                IMemoryCache<K, V> newInstance = (IMemoryCache<K, V>) c.newInstance();
+                memCache = newInstance;
+                memCache.initialize(this);
+            }
+            catch (Exception e)
+            {
+                log.warn("Failed to init mem cache, using: LRUMemoryCache", e);
+
+                this.memCache = new LRUMemoryCache<>();
+                this.memCache.initialize(this);
+            }
+        }
+        else
+        {
+            log.warn("Refusing to create memory cache -- already exists.");
+        }
+    }
+
+    /**
+     * Access to the memory cache for instrumentation.
+     * <p>
+     * @return the MemoryCache implementation
+     */
+    public IMemoryCache<K, V> getMemoryCache()
+    {
+        return memCache;
+    }
+
+    /**
+     * Number of times a requested item was found in the memory cache.
+     * <p>
+     * @return number of hits in memory
+     */
+    public long getHitCountRam()
+    {
+        return hitCountRam.get();
+    }
+
+    /**
+     * Number of times a requested item was found in and auxiliary cache.
+     * @return number of auxiliary hits.
+     */
+    public long getHitCountAux()
+    {
+        return hitCountAux.get();
+    }
+
+    /**
+     * Number of times a requested element was not found.
+     * @return number of misses.
+     */
+    public long getMissCountNotFound()
+    {
+        return missCountNotFound.get();
+    }
+
+    /**
+     * Number of times a requested element was found but was expired.
+     * @return number of found but expired gets.
+     */
+    public long getMissCountExpired()
+    {
+        return missCountExpired.get();
+    }
+
+    /**
+     * @return Returns the updateCount.
+     */
+    public long getUpdateCount()
+    {
+        return updateCount.get();
+    }
+
+    /**
+     * Sets the key matcher used by get matching.
+     * <p>
+     * @param keyMatcher
+     */
+    @Override
+    public void setKeyMatcher(IKeyMatcher<K> keyMatcher)
+    {
+        if (keyMatcher != null)
+        {
+            this.keyMatcher = keyMatcher;
+        }
+    }
+
+    /**
+     * Returns the key matcher used by get matching.
+     * <p>
+     * @return keyMatcher
+     */
+    public IKeyMatcher<K> getKeyMatcher()
+    {
+        return this.keyMatcher;
+    }
+
+    /**
+     * This returns the stats.
+     * <p>
+     * @return getStats()
+     */
+    @Override
+    public String toString()
+    {
+        return getStats();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/CompositeCacheConfigurator.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/CompositeCacheConfigurator.java
new file mode 100644
index 0000000..984149f
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/CompositeCacheConfigurator.java
@@ -0,0 +1,535 @@
+package org.apache.commons.jcs3.engine.control;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+import java.util.StringTokenizer;
+
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCache;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheConfigurator;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheFactory;
+import org.apache.commons.jcs3.engine.behavior.ICache;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes;
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.engine.behavior.IRequireScheduler;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+import org.apache.commons.jcs3.engine.match.KeyMatcherPatternImpl;
+import org.apache.commons.jcs3.engine.match.behavior.IKeyMatcher;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.config.OptionConverter;
+import org.apache.commons.jcs3.utils.config.PropertySetter;
+
+/**
+ * This class configures JCS based on a properties object.
+ * <p>
+ * This class is based on the log4j class org.apache.log4j.PropertyConfigurator which was made by:
+ * "Luke Blanshard" &lt;Luke@quiq.com&gt;"Mark DONSZELMANN" &lt;Mark.Donszelmann@cern.ch&gt;"Anders Kristensen"
+ * &lt;akristensen@dynamicsoft.com&gt;
+ */
+public class CompositeCacheConfigurator
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog( CompositeCacheConfigurator.class );
+
+    /** The prefix of relevant system properties */
+    protected static final String SYSTEM_PROPERTY_KEY_PREFIX = "jcs";
+
+    /** normal region prefix */
+    protected static final String REGION_PREFIX = "jcs.region.";
+
+    /** system region prefix. might not be used */
+    protected static final String SYSTEM_REGION_PREFIX = "jcs.system.";
+
+    /** auxiliary prefix */
+    protected static final String AUXILIARY_PREFIX = "jcs.auxiliary.";
+
+    /** .attributes */
+    protected static final String ATTRIBUTE_PREFIX = ".attributes";
+
+    /** .cacheattributes */
+    protected static final String CACHE_ATTRIBUTE_PREFIX = ".cacheattributes";
+
+    /** .elementattributes */
+    protected static final String ELEMENT_ATTRIBUTE_PREFIX = ".elementattributes";
+
+    /**
+     * jcs.auxiliary.NAME.keymatcher=CLASSNAME
+     * <p>
+     * jcs.auxiliary.NAME.keymatcher.attributes.CUSTOMPROPERTY=VALUE
+     */
+    public static final String KEY_MATCHER_PREFIX = ".keymatcher";
+
+    /**
+     * Constructor for the CompositeCacheConfigurator object
+     */
+    public CompositeCacheConfigurator()
+    {
+        // empty
+    }
+
+    /**
+     * Create caches used internally. System status gives them creation priority.
+     *<p>
+     * @param props Configuration properties
+     * @param ccm Cache hub
+     */
+    protected void parseSystemRegions( Properties props, CompositeCacheManager ccm )
+    {
+        for (String key : props.stringPropertyNames() )
+        {
+            if ( key.startsWith( SYSTEM_REGION_PREFIX ) && key.indexOf( "attributes" ) == -1 )
+            {
+                String regionName = key.substring( SYSTEM_REGION_PREFIX.length() );
+                String auxiliaries = OptionConverter.findAndSubst( key, props );
+                ICache<?, ?> cache;
+                synchronized ( regionName )
+                {
+                    cache = parseRegion( props, ccm, regionName, auxiliaries, null, SYSTEM_REGION_PREFIX );
+                }
+                ccm.addCache( regionName, cache );
+            }
+        }
+    }
+
+    /**
+     * Parse region elements.
+     *<p>
+     * @param props Configuration properties
+     * @param ccm Cache hub
+     */
+    protected void parseRegions( Properties props, CompositeCacheManager ccm )
+    {
+        List<String> regionNames = new ArrayList<>();
+
+        for (String key : props.stringPropertyNames() )
+        {
+            if ( key.startsWith( REGION_PREFIX ) && key.indexOf( "attributes" ) == -1 )
+            {
+                String regionName = key.substring( REGION_PREFIX.length() );
+                regionNames.add( regionName );
+                String auxiliaries = OptionConverter.findAndSubst( key, props );
+                ICache<?, ?> cache;
+                synchronized ( regionName )
+                {
+                    cache = parseRegion( props, ccm, regionName, auxiliaries );
+                }
+                ccm.addCache( regionName, cache );
+            }
+        }
+
+        log.info( "Parsed regions {0}", regionNames );
+    }
+
+    /**
+     * Create cache region.
+     *<p>
+     * @param props Configuration properties
+     * @param ccm Cache hub
+     * @param regName Name of the cache region
+     * @param auxiliaries Comma separated list of auxiliaries
+     *
+     * @return CompositeCache
+     */
+    protected <K, V> CompositeCache<K, V> parseRegion(
+            Properties props, CompositeCacheManager ccm, String regName, String auxiliaries )
+    {
+        return parseRegion( props, ccm, regName, auxiliaries, null, REGION_PREFIX );
+    }
+
+    /**
+     * Get all the properties for a region and configure its cache.
+     * <p>
+     * This method tells the other parse method the name of the region prefix.
+     *<p>
+     * @param props Configuration properties
+     * @param ccm Cache hub
+     * @param regName Name of the cache region
+     * @param auxiliaries Comma separated list of auxiliaries
+     * @param cca Cache configuration
+     *
+     * @return CompositeCache
+     */
+    protected <K, V> CompositeCache<K, V> parseRegion(
+            Properties props, CompositeCacheManager ccm, String regName, String auxiliaries,
+            ICompositeCacheAttributes cca )
+    {
+        return parseRegion( props, ccm, regName, auxiliaries, cca, REGION_PREFIX );
+    }
+
+    /**
+     * Get all the properties for a region and configure its cache.
+     *<p>
+     * @param props Configuration properties
+     * @param ccm Cache hub
+     * @param regName Name of the cache region
+     * @param auxiliaries Comma separated list of auxiliaries
+     * @param cca Cache configuration
+     * @param regionPrefix Prefix for the region
+     *
+     * @return CompositeCache
+     */
+    protected <K, V> CompositeCache<K, V> parseRegion(
+            Properties props, CompositeCacheManager ccm, String regName, String auxiliaries,
+            ICompositeCacheAttributes cca, String regionPrefix )
+    {
+        // First, create or get the cache and element attributes, and create
+        // the cache.
+        IElementAttributes ea = parseElementAttributes( props, regName,
+                ccm.getDefaultElementAttributes(), regionPrefix );
+
+        ICompositeCacheAttributes instantiationCca = cca == null
+                ? parseCompositeCacheAttributes(props, regName, ccm.getDefaultCacheAttributes(), regionPrefix)
+                : cca;
+        CompositeCache<K, V> cache = newCache(instantiationCca, ea);
+
+        // Inject cache manager
+        cache.setCompositeCacheManager(ccm);
+
+        // Inject scheduler service
+        cache.setScheduledExecutorService(ccm.getScheduledExecutorService());
+
+        // Inject element event queue
+        cache.setElementEventQueue(ccm.getElementEventQueue());
+
+        if (cache.getMemoryCache() instanceof IRequireScheduler)
+        {
+            ((IRequireScheduler)cache.getMemoryCache()).setScheduledExecutorService(
+                    ccm.getScheduledExecutorService());
+        }
+
+        if (auxiliaries != null)
+        {
+            // Next, create the auxiliaries for the new cache
+            List<AuxiliaryCache<K, V>> auxList = new ArrayList<>();
+
+            log.debug( "Parsing region name \"{0}\", value \"{1}\"", regName, auxiliaries );
+
+            // We must skip over ',' but not white space
+            StringTokenizer st = new StringTokenizer( auxiliaries, "," );
+
+            // If value is not in the form ", appender.." or "", then we should set
+            // the priority of the category.
+
+            if ( !( auxiliaries.startsWith( "," ) || auxiliaries.equals( "" ) ) )
+            {
+                // just to be on the safe side...
+                if ( !st.hasMoreTokens() )
+                {
+                    return null;
+                }
+            }
+
+            AuxiliaryCache<K, V> auxCache;
+            String auxName;
+            while ( st.hasMoreTokens() )
+            {
+                auxName = st.nextToken().trim();
+                if ( auxName == null || auxName.equals( "," ) )
+                {
+                    continue;
+                }
+                log.debug( "Parsing auxiliary named \"{0}\".", auxName );
+
+                auxCache = parseAuxiliary( props, ccm, auxName, regName );
+
+                if ( auxCache != null )
+                {
+                    if (auxCache instanceof IRequireScheduler)
+                    {
+                        ((IRequireScheduler) auxCache).setScheduledExecutorService(
+                                ccm.getScheduledExecutorService());
+                    }
+
+                    auxList.add( auxCache );
+                }
+            }
+
+            // Associate the auxiliaries with the cache
+            @SuppressWarnings("unchecked") // No generic arrays in java
+            AuxiliaryCache<K, V>[] auxArray = auxList.toArray( new AuxiliaryCache[0] );
+            cache.setAuxCaches( auxArray );
+        }
+
+        // Return the new cache
+        return cache;
+    }
+
+    protected <K, V> CompositeCache<K, V> newCache(
+            ICompositeCacheAttributes cca, IElementAttributes ea)
+    {
+        return new CompositeCache<>( cca, ea );
+    }
+
+    /**
+     * Get an ICompositeCacheAttributes for the listed region.
+     *<p>
+     * @param props Configuration properties
+     * @param regName the region name
+     * @param defaultCCAttr the default cache attributes
+     *
+     * @return ICompositeCacheAttributes
+     */
+    protected ICompositeCacheAttributes parseCompositeCacheAttributes( Properties props,
+            String regName, ICompositeCacheAttributes defaultCCAttr )
+    {
+        return parseCompositeCacheAttributes( props, regName, defaultCCAttr, REGION_PREFIX );
+    }
+
+    /**
+     * Get the main attributes for a region.
+     *<p>
+     * @param props Configuration properties
+     * @param regName the region name
+     * @param defaultCCAttr the default cache attributes
+     * @param regionPrefix the region prefix
+     *
+     * @return ICompositeCacheAttributes
+     */
+    protected ICompositeCacheAttributes parseCompositeCacheAttributes( Properties props,
+            String regName, ICompositeCacheAttributes defaultCCAttr, String regionPrefix )
+    {
+        ICompositeCacheAttributes ccAttr;
+
+        String attrName = regionPrefix + regName + CACHE_ATTRIBUTE_PREFIX;
+
+        // auxFactory was not previously initialized.
+        // String prefix = regionPrefix + regName + ATTRIBUTE_PREFIX;
+        ccAttr = OptionConverter.instantiateByKey( props, attrName, null );
+
+        if ( ccAttr == null )
+        {
+            log.info( "No special CompositeCacheAttributes class defined for "
+                    + "key [{0}], using default class.", attrName );
+
+            ccAttr = defaultCCAttr;
+        }
+
+        log.debug( "Parsing options for \"{0}\"", attrName );
+
+        PropertySetter.setProperties( ccAttr, props, attrName + "." );
+        ccAttr.setCacheName( regName );
+
+        log.debug( "End of parsing for \"{0}\"", attrName );
+
+        // GET CACHE FROM FACTORY WITH ATTRIBUTES
+        ccAttr.setCacheName( regName );
+        return ccAttr;
+    }
+
+    /**
+     * Create the element attributes from the properties object for a cache region.
+     *<p>
+     * @param props Configuration properties
+     * @param regName the region name
+     * @param defaultEAttr the default element attributes
+     * @param regionPrefix the region prefix
+     *
+     * @return IElementAttributes
+     */
+    protected IElementAttributes parseElementAttributes( Properties props, String regName,
+            IElementAttributes defaultEAttr, String regionPrefix )
+    {
+        IElementAttributes eAttr;
+
+        String attrName = regionPrefix + regName + CompositeCacheConfigurator.ELEMENT_ATTRIBUTE_PREFIX;
+
+        // auxFactory was not previously initialized.
+        // String prefix = regionPrefix + regName + ATTRIBUTE_PREFIX;
+        eAttr = OptionConverter.instantiateByKey( props, attrName, null );
+        if ( eAttr == null )
+        {
+            log.info( "No special ElementAttribute class defined for key [{0}], "
+                    + "using default class.", attrName );
+
+            eAttr = defaultEAttr;
+        }
+
+        log.debug( "Parsing options for \"{0}\"", attrName );
+
+        PropertySetter.setProperties( eAttr, props, attrName + "." );
+        // eAttr.setCacheName( regName );
+
+        log.debug( "End of parsing for \"{0}\"", attrName );
+
+        // GET CACHE FROM FACTORY WITH ATTRIBUTES
+        // eAttr.setCacheName( regName );
+        return eAttr;
+    }
+
+    /**
+     * Get an aux cache for the listed aux for a region.
+     *<p>
+     * @param props the configuration properties
+     * @param ccm Cache hub
+     * @param auxName the name of the auxiliary cache
+     * @param regName the name of the region.
+     * @return AuxiliaryCache
+     */
+    protected <K, V> AuxiliaryCache<K, V> parseAuxiliary( Properties props, CompositeCacheManager ccm,
+            String auxName, String regName )
+    {
+        log.debug( "parseAuxiliary {0}", auxName );
+
+        // GET CACHE
+        @SuppressWarnings("unchecked") // Common map for all caches
+        AuxiliaryCache<K, V> auxCache = (AuxiliaryCache<K, V>) ccm.getAuxiliaryCache(auxName, regName);
+
+        if (auxCache == null)
+        {
+            // GET FACTORY
+            AuxiliaryCacheFactory auxFac = ccm.registryFacGet( auxName );
+            if ( auxFac == null )
+            {
+                // auxFactory was not previously initialized.
+                String prefix = AUXILIARY_PREFIX + auxName;
+                auxFac = OptionConverter.instantiateByKey( props, prefix, null );
+                if ( auxFac == null )
+                {
+                    log.error( "Could not instantiate auxFactory named \"{0}\"", auxName );
+                    return null;
+                }
+
+                auxFac.setName( auxName );
+
+                if ( auxFac instanceof IRequireScheduler)
+                {
+                	((IRequireScheduler)auxFac).setScheduledExecutorService(ccm.getScheduledExecutorService());
+                }
+
+                auxFac.initialize();
+                ccm.registryFacPut( auxFac );
+            }
+
+            // GET ATTRIBUTES
+            AuxiliaryCacheAttributes auxAttr = ccm.registryAttrGet( auxName );
+            String attrName = AUXILIARY_PREFIX + auxName + ATTRIBUTE_PREFIX;
+            if ( auxAttr == null )
+            {
+                // auxFactory was not previously initialized.
+                String prefix = AUXILIARY_PREFIX + auxName + ATTRIBUTE_PREFIX;
+                auxAttr = OptionConverter.instantiateByKey( props, prefix, null );
+                if ( auxAttr == null )
+                {
+                    log.error( "Could not instantiate auxAttr named \"{0}\"", attrName );
+                    return null;
+                }
+                auxAttr.setName( auxName );
+                ccm.registryAttrPut( auxAttr );
+            }
+
+            auxAttr = auxAttr.clone();
+
+            log.debug( "Parsing options for \"{0}\"", attrName );
+
+            PropertySetter.setProperties( auxAttr, props, attrName + "." );
+            auxAttr.setCacheName( regName );
+
+            log.debug( "End of parsing for \"{0}\"", attrName );
+
+            // GET CACHE FROM FACTORY WITH ATTRIBUTES
+            auxAttr.setCacheName( regName );
+
+            String auxPrefix = AUXILIARY_PREFIX + auxName;
+
+            // CONFIGURE THE EVENT LOGGER
+            ICacheEventLogger cacheEventLogger =
+                    AuxiliaryCacheConfigurator.parseCacheEventLogger( props, auxPrefix );
+
+            // CONFIGURE THE ELEMENT SERIALIZER
+            IElementSerializer elementSerializer =
+                    AuxiliaryCacheConfigurator.parseElementSerializer( props, auxPrefix );
+
+            // CONFIGURE THE KEYMATCHER
+            //IKeyMatcher keyMatcher = parseKeyMatcher( props, auxPrefix );
+            // TODO add to factory interface
+
+            // Consider putting the compositeCache back in the factory interface
+            // since the manager may not know about it at this point.
+            // need to make sure the manager already has the cache
+            // before the auxiliary is created.
+            try
+            {
+                auxCache = auxFac.createCache( auxAttr, ccm, cacheEventLogger, elementSerializer );
+            }
+            catch (Exception e)
+            {
+                log.error( "Could not instantiate auxiliary cache named \"{0}\"", regName, e );
+                return null;
+            }
+
+            ccm.addAuxiliaryCache(auxName, regName, auxCache);
+        }
+
+        return auxCache;
+    }
+
+    /**
+     * Any property values will be replaced with system property values that match the key.
+     * <p>
+     * @param props
+     */
+    protected static void overrideWithSystemProperties( Properties props )
+    {
+        // override any setting with values from the system properties.
+        Properties sysProps = System.getProperties();
+        for (String key : sysProps.stringPropertyNames())
+        {
+            if ( key.startsWith( SYSTEM_PROPERTY_KEY_PREFIX ) )
+            {
+                log.info( "Using system property [[{0}] [{1}]]", () -> key,
+                        () -> sysProps.getProperty( key ) );
+                props.setProperty( key, sysProps.getProperty( key ) );
+            }
+        }
+    }
+
+    /**
+     * Creates a custom key matcher if one is defined.  Else, it uses the default.
+     * <p>
+     * @param props
+     * @param auxPrefix - ex. AUXILIARY_PREFIX + auxName
+     * @return IKeyMatcher
+     */
+    protected <K> IKeyMatcher<K> parseKeyMatcher( Properties props, String auxPrefix )
+    {
+
+        // auxFactory was not previously initialized.
+        String keyMatcherClassName = auxPrefix + KEY_MATCHER_PREFIX;
+        IKeyMatcher<K> keyMatcher = OptionConverter.instantiateByKey( props, keyMatcherClassName, null );
+        if ( keyMatcher != null )
+        {
+            String attributePrefix = auxPrefix + KEY_MATCHER_PREFIX + ATTRIBUTE_PREFIX;
+            PropertySetter.setProperties( keyMatcher, props, attributePrefix + "." );
+            log.info( "Using custom key matcher [{0}] for auxiliary [{1}]", keyMatcher, auxPrefix );
+        }
+        else
+        {
+            // use the default standard serializer
+            keyMatcher = new KeyMatcherPatternImpl<>();
+            log.info( "Using standard key matcher [{0}] for auxiliary [{1}]", keyMatcher, auxPrefix );
+        }
+        return keyMatcher;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/CompositeCacheManager.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/CompositeCacheManager.java
new file mode 100644
index 0000000..60bed12
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/CompositeCacheManager.java
@@ -0,0 +1,927 @@
+package org.apache.commons.jcs3.engine.control;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.management.ManagementFactory;
+import java.security.AccessControlException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import org.apache.commons.jcs3.access.exception.CacheException;
+import org.apache.commons.jcs3.admin.JCSAdminBean;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCache;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheFactory;
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheConstants;
+import org.apache.commons.jcs3.engine.CompositeCacheAttributes;
+import org.apache.commons.jcs3.engine.ElementAttributes;
+import org.apache.commons.jcs3.engine.behavior.ICache;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+import org.apache.commons.jcs3.engine.behavior.IProvideScheduler;
+import org.apache.commons.jcs3.engine.behavior.IShutdownObserver;
+import org.apache.commons.jcs3.engine.behavior.ICacheType.CacheType;
+import org.apache.commons.jcs3.engine.control.event.ElementEventQueue;
+import org.apache.commons.jcs3.engine.control.event.behavior.IElementEventQueue;
+import org.apache.commons.jcs3.engine.stats.CacheStats;
+import org.apache.commons.jcs3.engine.stats.behavior.ICacheStats;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.config.OptionConverter;
+import org.apache.commons.jcs3.utils.threadpool.DaemonThreadFactory;
+import org.apache.commons.jcs3.utils.threadpool.ThreadPoolManager;
+import org.apache.commons.jcs3.utils.timing.ElapsedTimer;
+
+/**
+ * Manages a composite cache. This provides access to caches and is the primary way to shutdown the
+ * caching system as a whole.
+ * <p>
+ * The composite cache manager is responsible for creating / configuring cache regions. It serves as
+ * a factory for the ComositeCache class. The CompositeCache is the core of JCS, the hub for various
+ * auxiliaries.
+ */
+public class CompositeCacheManager
+    implements IRemoteCacheConstants, ICompositeCacheManager, IProvideScheduler
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog( CompositeCacheManager.class );
+
+    /** JMX object name */
+    public static final String JMX_OBJECT_NAME = "org.apache.commons.jcs3:type=JCSAdminBean";
+
+    /** This is the name of the config file that we will look for by default. */
+    private static final String DEFAULT_CONFIG = "/cache.ccf";
+
+    /** default region prefix */
+    private static final String DEFAULT_REGION = "jcs.default";
+
+    /** Should we use system property substitutions. */
+    private static final boolean DEFAULT_USE_SYSTEM_PROPERTIES = true;
+
+    /** Once configured, you can force a reconfiguration of sorts. */
+    private static final boolean DEFAULT_FORCE_RECONFIGURATION = false;
+
+    /** Caches managed by this cache manager */
+    private final ConcurrentMap<String, ICache<?, ?>> caches = new ConcurrentHashMap<>();
+
+    /** Number of clients accessing this cache manager */
+    private final AtomicInteger clients = new AtomicInteger(0);
+
+    /** Default cache attributes for this cache manager */
+    private ICompositeCacheAttributes defaultCacheAttr = new CompositeCacheAttributes();
+
+    /** Default element attributes for this cache manager */
+    private IElementAttributes defaultElementAttr = new ElementAttributes();
+
+    /** Used to keep track of configured auxiliaries */
+    private final ConcurrentMap<String, AuxiliaryCacheFactory> auxiliaryFactoryRegistry =
+        new ConcurrentHashMap<>( );
+
+    /** Used to keep track of attributes for auxiliaries. */
+    private final ConcurrentMap<String, AuxiliaryCacheAttributes> auxiliaryAttributeRegistry =
+        new ConcurrentHashMap<>( );
+
+    /** Used to keep track of configured auxiliaries */
+    private final ConcurrentMap<String, AuxiliaryCache<?, ?>> auxiliaryCaches =
+        new ConcurrentHashMap<>( );
+
+    /** Properties with which this manager was configured. This is exposed for other managers. */
+    private Properties configurationProperties;
+
+    /** The default auxiliary caches to be used if not preconfigured */
+    private String defaultAuxValues;
+
+    /** The Singleton Instance */
+    private static CompositeCacheManager instance;
+
+    /** Stack for those waiting for notification of a shutdown. */
+    private final LinkedBlockingDeque<IShutdownObserver> shutdownObservers = new LinkedBlockingDeque<>();
+
+    /** The central background scheduler. */
+    private ScheduledExecutorService scheduledExecutor;
+
+    /** The central event queue. */
+    private IElementEventQueue elementEventQueue;
+
+    /** Shutdown hook thread instance */
+    private Thread shutdownHook;
+
+    /** Indicates whether the instance has been initialized. */
+    private boolean isInitialized = false;
+
+    /** Indicates whether configure has been called. */
+    private boolean isConfigured = false;
+
+    /** Indicates whether JMX bean has been registered. */
+    private boolean isJMXRegistered = false;
+
+    private String jmxName = JMX_OBJECT_NAME;
+
+    /**
+     * Gets the CacheHub instance. For backward compatibility, if this creates the instance it will
+     * attempt to configure it with the default configuration. If you want to configure from your
+     * own source, use {@link #getUnconfiguredInstance}and then call {@link #configure}
+     * <p>
+     * @return CompositeCacheManager
+     * @throws CacheException if the configuration cannot be loaded
+     */
+    public static synchronized CompositeCacheManager getInstance() throws CacheException
+    {
+        return getInstance( DEFAULT_CONFIG );
+    }
+
+    /**
+     * Initializes the cache manager using the props file for the given name.
+     * <p>
+     * @param propsFilename
+     * @return CompositeCacheManager configured from the give propsFileName
+     * @throws CacheException if the configuration cannot be loaded
+     */
+    public static synchronized CompositeCacheManager getInstance( String propsFilename ) throws CacheException
+    {
+        if ( instance == null )
+        {
+            log.info( "Instance is null, creating with config [{0}]", propsFilename );
+            instance = createInstance();
+        }
+
+        if (!instance.isInitialized())
+        {
+            instance.initialize();
+        }
+
+        if (!instance.isConfigured())
+        {
+            instance.configure( propsFilename );
+        }
+
+        instance.clients.incrementAndGet();
+
+        return instance;
+    }
+
+    /**
+     * Get a CacheHub instance which is not configured. If an instance already exists, it will be
+     * returned.
+     *<p>
+     * @return CompositeCacheManager
+     */
+    public static synchronized CompositeCacheManager getUnconfiguredInstance()
+    {
+        if ( instance == null )
+        {
+            log.info( "Instance is null, returning unconfigured instance" );
+            instance = createInstance();
+        }
+
+        if (!instance.isInitialized())
+        {
+            instance.initialize();
+        }
+
+        instance.clients.incrementAndGet();
+
+        return instance;
+    }
+
+    /**
+     * Simple factory method, must override in subclasses so getInstance creates / returns the
+     * correct object.
+     * <p>
+     * @return CompositeCacheManager
+     */
+    protected static CompositeCacheManager createInstance()
+    {
+        return new CompositeCacheManager();
+    }
+
+    /**
+     * Default constructor
+     */
+    protected CompositeCacheManager()
+    {
+        // empty
+    }
+
+    /** Creates a shutdown hook and starts the scheduler service */
+    protected void initialize()
+    {
+        if (!isInitialized)
+        {
+            this.shutdownHook = new Thread(() -> {
+                if ( isInitialized() )
+                {
+                    log.info("Shutdown hook activated. Shutdown was not called. Shutting down JCS.");
+                    shutDown();
+                }
+            });
+            try
+            {
+                Runtime.getRuntime().addShutdownHook( shutdownHook );
+            }
+            catch ( AccessControlException e )
+            {
+                log.error( "Could not register shutdown hook.", e );
+            }
+
+            this.scheduledExecutor = Executors.newScheduledThreadPool(4,
+                    new DaemonThreadFactory("JCS-Scheduler-", Thread.MIN_PRIORITY));
+
+            // Register JMX bean
+            if (!isJMXRegistered && jmxName != null)
+            {
+                MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+                JCSAdminBean adminBean = new JCSAdminBean(this);
+                try
+                {
+                    ObjectName jmxObjectName = new ObjectName(jmxName);
+                    mbs.registerMBean(adminBean, jmxObjectName);
+                    isJMXRegistered = true;
+                }
+                catch (Exception e)
+                {
+                    log.warn( "Could not register JMX bean.", e );
+                }
+            }
+
+            isInitialized = true;
+        }
+    }
+
+    /**
+     * Get the element event queue
+     *
+     * @return the elementEventQueue
+     */
+    public IElementEventQueue getElementEventQueue()
+    {
+        return elementEventQueue;
+    }
+
+    /**
+     * Get the scheduler service
+     *
+     * @return the scheduledExecutor
+     */
+    @Override
+    public ScheduledExecutorService getScheduledExecutorService()
+    {
+        return scheduledExecutor;
+    }
+
+    /**
+     * Configure with default properties file
+     * @throws CacheException if the configuration cannot be loaded
+     */
+    public void configure() throws CacheException
+    {
+        configure( DEFAULT_CONFIG );
+    }
+
+    /**
+     * Configure from specific properties file.
+     * <p>
+     * @param propFile Path <u>within classpath </u> to load configuration from
+     * @throws CacheException if the configuration cannot be loaded
+     */
+    public void configure( String propFile ) throws CacheException
+    {
+        log.info( "Creating cache manager from config file: {0}", propFile );
+
+        Properties props = new Properties();
+
+        try (InputStream is = getClass().getResourceAsStream( propFile ))
+        {
+            props.load( is );
+            log.debug( "File [{0}] contained {1} properties", () -> propFile, () -> props.size());
+        }
+        catch ( IOException ex )
+        {
+            throw new CacheException("Failed to load properties for name [" + propFile + "]", ex);
+        }
+
+        configure( props );
+    }
+
+    /**
+     * Configure from properties object.
+     * <p>
+     * This method will call configure, instructing it to use system properties as a default.
+     * @param props
+     */
+    public void configure( Properties props )
+    {
+        configure( props, DEFAULT_USE_SYSTEM_PROPERTIES );
+    }
+
+    /**
+     * Configure from properties object, overriding with values from the system properties if
+     * instructed.
+     * <p>
+     * You can override a specific value by passing in a system property:
+     * <p>
+     * For example, you could override this value in the cache.ccf file by starting up your program
+     * with the argument: -Djcs.auxiliary.LTCP.attributes.TcpListenerPort=1111
+     * <p>
+     * @param props
+     * @param useSystemProperties -- if true, values starting with jcs will be put into the props
+     *            file prior to configuring the cache.
+     */
+    public void configure( Properties props, boolean useSystemProperties )
+    {
+        configure( props, useSystemProperties, DEFAULT_FORCE_RECONFIGURATION );
+    }
+
+    /**
+     * Configure from properties object, overriding with values from the system properties if
+     * instructed.
+     * <p>
+     * You can override a specific value by passing in a system property:
+     * <p>
+     * For example, you could override this value in the cache.ccf file by starting up your program
+     * with the argument: -Djcs.auxiliary.LTCP.attributes.TcpListenerPort=1111
+     * <p>
+     * @param props
+     * @param useSystemProperties -- if true, values starting with jcs will be put into the props
+     *            file prior to configuring the cache.
+     * @param forceReconfiguration - if the manager is already configured, we will try again. This
+     *            may not work properly.
+     */
+    public synchronized void configure( Properties props, boolean useSystemProperties, boolean forceReconfiguration )
+    {
+        if ( props == null )
+        {
+            log.error( "No properties found. Please configure the cache correctly." );
+            return;
+        }
+
+        if ( isConfigured )
+        {
+            if ( !forceReconfiguration )
+            {
+                log.debug( "Configure called after the manager has been configured.  "
+                         + "Force reconfiguration is false. Doing nothing" );
+                return;
+            }
+            else
+            {
+                log.info( "Configure called after the manager has been configured.  "
+                        + "Force reconfiguration is true. Reconfiguring as best we can." );
+            }
+        }
+        if ( useSystemProperties )
+        {
+            CompositeCacheConfigurator.overrideWithSystemProperties( props );
+        }
+        doConfigure( props );
+    }
+
+    /**
+     * Configure the cache using the supplied properties.
+     * <p>
+     * @param properties assumed not null
+     */
+    private void doConfigure( Properties properties )
+    {
+        // We will expose this for managers that need raw properties.
+        this.configurationProperties = properties;
+
+        // set the props value and then configure the ThreadPoolManager
+        ThreadPoolManager.setProps( properties );
+        ThreadPoolManager poolMgr = ThreadPoolManager.getInstance();
+        log.debug( "ThreadPoolManager = {0}", poolMgr);
+
+        // Create event queue
+        this.elementEventQueue = new ElementEventQueue();
+
+        // configure the cache
+        CompositeCacheConfigurator configurator = newConfigurator();
+
+        ElapsedTimer timer = new ElapsedTimer();
+
+        // set default value list
+        this.defaultAuxValues = OptionConverter.findAndSubst( CompositeCacheManager.DEFAULT_REGION,
+                properties );
+
+        log.info( "Setting default auxiliaries to \"{0}\"", this.defaultAuxValues );
+
+        // set default cache attr
+        this.defaultCacheAttr = configurator.parseCompositeCacheAttributes( properties, "",
+                new CompositeCacheAttributes(), DEFAULT_REGION );
+
+        log.info( "setting defaultCompositeCacheAttributes to {0}", this.defaultCacheAttr );
+
+        // set default element attr
+        this.defaultElementAttr = configurator.parseElementAttributes( properties, "",
+                new ElementAttributes(), DEFAULT_REGION );
+
+        log.info( "setting defaultElementAttributes to {0}", this.defaultElementAttr );
+
+        // set up system caches to be used by non system caches
+        // need to make sure there is no circularity of reference
+        configurator.parseSystemRegions( properties, this );
+
+        // setup preconfigured caches
+        configurator.parseRegions( properties, this );
+
+        log.info( "Finished configuration in {0} ms.", () -> timer.getElapsedTime());
+
+        isConfigured = true;
+    }
+
+    /**
+     * Gets the defaultCacheAttributes attribute of the CacheHub object
+     * <p>
+     * @return The defaultCacheAttributes value
+     */
+    public ICompositeCacheAttributes getDefaultCacheAttributes()
+    {
+        return this.defaultCacheAttr.clone();
+    }
+
+    /**
+     * Gets the defaultElementAttributes attribute of the CacheHub object
+     * <p>
+     * @return The defaultElementAttributes value
+     */
+    public IElementAttributes getDefaultElementAttributes()
+    {
+        return this.defaultElementAttr.clone();
+    }
+
+    /**
+     * Gets the cache attribute of the CacheHub object
+     * <p>
+     * @param cacheName
+     * @return CompositeCache -- the cache region controller
+     */
+    @Override
+    public <K, V> CompositeCache<K, V> getCache( String cacheName )
+    {
+        return getCache( cacheName, getDefaultCacheAttributes() );
+    }
+
+    /**
+     * Gets the cache attribute of the CacheHub object
+     * <p>
+     * @param cacheName
+     * @param cattr
+     * @return CompositeCache
+     */
+    public <K, V> CompositeCache<K, V> getCache( String cacheName, ICompositeCacheAttributes cattr )
+    {
+        cattr.setCacheName( cacheName );
+        return getCache( cattr, getDefaultElementAttributes() );
+    }
+
+    /**
+     * Gets the cache attribute of the CacheHub object
+     * <p>
+     * @param cacheName
+     * @param cattr
+     * @param attr
+     * @return CompositeCache
+     */
+    public <K, V> CompositeCache<K, V>  getCache( String cacheName, ICompositeCacheAttributes cattr, IElementAttributes attr )
+    {
+        cattr.setCacheName( cacheName );
+        return getCache( cattr, attr );
+    }
+
+    /**
+     * Gets the cache attribute of the CacheHub object
+     * <p>
+     * @param cattr
+     * @return CompositeCache
+     */
+    public <K, V> CompositeCache<K, V>  getCache( ICompositeCacheAttributes cattr )
+    {
+        return getCache( cattr, getDefaultElementAttributes() );
+    }
+
+    /**
+     * If the cache has already been created, then the CacheAttributes and the element Attributes
+     * will be ignored. Currently there is no overriding the CacheAttributes once it is set up. You
+     * can change the default ElementAttributes for a region later.
+     * <p>
+     * Overriding the default elemental attributes will require changing the way the attributes are
+     * assigned to elements. Get cache creates a cache with defaults if none are specified. We might
+     * want to create separate method for creating/getting. . .
+     * <p>
+     * @param cattr
+     * @param attr
+     * @return CompositeCache
+     */
+    @SuppressWarnings("unchecked") // Need to cast because of common map for all caches
+    public <K, V> CompositeCache<K, V>  getCache( ICompositeCacheAttributes cattr, IElementAttributes attr )
+    {
+        log.debug( "attr = {0}", attr );
+
+        CompositeCache<K, V> cache = (CompositeCache<K, V>) caches.computeIfAbsent(cattr.getCacheName(),
+                cacheName -> {
+            CompositeCacheConfigurator configurator = newConfigurator();
+            return configurator.parseRegion( this.getConfigurationProperties(), this, cacheName,
+                                              this.defaultAuxValues, cattr );
+        });
+
+        return cache;
+    }
+
+    protected CompositeCacheConfigurator newConfigurator() {
+        return new CompositeCacheConfigurator();
+    }
+
+    /**
+     * @param name
+     */
+    public void freeCache( String name )
+    {
+        freeCache( name, false );
+    }
+
+    /**
+     * @param name
+     * @param fromRemote
+     */
+    public void freeCache( String name, boolean fromRemote )
+    {
+        CompositeCache<?, ?> cache = (CompositeCache<?, ?>) caches.remove( name );
+
+        if ( cache != null )
+        {
+            cache.dispose( fromRemote );
+        }
+    }
+
+    /**
+     * Calls freeCache on all regions
+     */
+    public void shutDown()
+    {
+        synchronized (CompositeCacheManager.class)
+        {
+            // shutdown element event queue
+            if (this.elementEventQueue != null)
+            {
+                this.elementEventQueue.dispose();
+            }
+
+            // shutdown all scheduled jobs
+            this.scheduledExecutor.shutdownNow();
+
+            // shutdown all thread pools
+            ThreadPoolManager.dispose();
+
+            // notify any observers
+            IShutdownObserver observer = null;
+            while ((observer = shutdownObservers.poll()) != null)
+            {
+                observer.shutdown();
+            }
+
+            // Unregister JMX bean
+            if (isJMXRegistered)
+            {
+                MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+                try
+                {
+                    ObjectName jmxObjectName = new ObjectName(jmxName);
+                    mbs.unregisterMBean(jmxObjectName);
+                }
+                catch (Exception e)
+                {
+                    log.warn( "Could not unregister JMX bean.", e );
+                }
+
+                isJMXRegistered = false;
+            }
+
+            // do the traditional shutdown of the regions.
+            getCacheNames().forEach(this::freeCache);
+
+            // shut down auxiliaries
+            for (String key : auxiliaryCaches.keySet())
+            {
+                try
+                {
+                    freeAuxiliaryCache(key);
+                }
+                catch (IOException e)
+                {
+                    log.warn("Auxiliary cache {0} failed to shut down", key, e);
+                }
+            }
+
+            // shut down factories
+            auxiliaryFactoryRegistry.values().forEach(AuxiliaryCacheFactory::dispose);
+
+            auxiliaryAttributeRegistry.clear();
+            auxiliaryFactoryRegistry.clear();
+
+            if (shutdownHook != null)
+            {
+                try
+                {
+                    Runtime.getRuntime().removeShutdownHook(shutdownHook);
+                }
+                catch (IllegalStateException e)
+                {
+                    // May fail if the JVM is already shutting down
+                }
+
+                this.shutdownHook = null;
+            }
+
+            isConfigured = false;
+            isInitialized = false;
+        }
+    }
+
+    /** */
+    public void release()
+    {
+        release( false );
+    }
+
+    /**
+     * @param fromRemote
+     */
+    private void release( boolean fromRemote )
+    {
+        synchronized ( CompositeCacheManager.class )
+        {
+            // Wait until called by the last client
+            if ( clients.decrementAndGet() > 0 )
+            {
+                log.debug( "Release called, but {0} remain", clients);
+                return;
+            }
+
+            log.debug( "Last client called release. There are {0} caches which will be disposed",
+                    () -> caches.size());
+
+            caches.values().stream()
+                .filter(cache -> cache != null)
+                .forEach(cache -> {
+                    ((CompositeCache<?, ?>)cache).dispose( fromRemote );
+                });
+        }
+    }
+
+    /**
+     * Returns a list of the current cache names.
+     * @return Set<String>
+     */
+    public Set<String> getCacheNames()
+    {
+        return caches.keySet();
+    }
+
+    /**
+     * @return ICacheType.CACHE_HUB
+     */
+    public CacheType getCacheType()
+    {
+        return CacheType.CACHE_HUB;
+    }
+
+    /**
+     * @param auxFac
+     */
+    public void registryFacPut( AuxiliaryCacheFactory auxFac )
+    {
+        auxiliaryFactoryRegistry.put( auxFac.getName(), auxFac );
+    }
+
+    /**
+     * @param name
+     * @return AuxiliaryCacheFactory
+     */
+    public AuxiliaryCacheFactory registryFacGet( String name )
+    {
+        return auxiliaryFactoryRegistry.get( name );
+    }
+
+    /**
+     * @param auxAttr
+     */
+    public void registryAttrPut( AuxiliaryCacheAttributes auxAttr )
+    {
+        auxiliaryAttributeRegistry.put( auxAttr.getName(), auxAttr );
+    }
+
+    /**
+     * @param name
+     * @return AuxiliaryCacheAttributes
+     */
+    public AuxiliaryCacheAttributes registryAttrGet( String name )
+    {
+        return auxiliaryAttributeRegistry.get( name );
+    }
+
+    /**
+     * Add a cache to the map of registered caches
+     *
+     * @param cacheName the region name
+     * @param cache the cache instance
+     */
+    public void addCache(String cacheName, ICache<?, ?> cache)
+    {
+        caches.put(cacheName, cache);
+    }
+
+    /**
+     * Add a cache to the map of registered auxiliary caches
+     *
+     * @param auxName the auxiliary name
+     * @param cacheName the region name
+     * @param cache the cache instance
+     */
+    public void addAuxiliaryCache(String auxName, String cacheName, AuxiliaryCache<?, ?> cache)
+    {
+        String key = String.format("aux.%s.region.%s", auxName, cacheName);
+        auxiliaryCaches.put(key, cache);
+    }
+
+    /**
+     * Get a cache from the map of registered auxiliary caches
+     *
+     * @param auxName the auxiliary name
+     * @param cacheName the region name
+     *
+     * @return the cache instance
+     */
+    @Override
+    @SuppressWarnings("unchecked") // because of common map for all auxiliary caches
+    public <K, V> AuxiliaryCache<K, V> getAuxiliaryCache(String auxName, String cacheName)
+    {
+        String key = String.format("aux.%s.region.%s", auxName, cacheName);
+        return (AuxiliaryCache<K, V>) auxiliaryCaches.get(key);
+    }
+
+    /**
+     * Dispose a cache and remove it from the map of registered auxiliary caches
+     *
+     * @param auxName the auxiliary name
+     * @param cacheName the region name
+     * @throws IOException if disposing of the cache fails
+     */
+    public void freeAuxiliaryCache(String auxName, String cacheName) throws IOException
+    {
+        String key = String.format("aux.%s.region.%s", auxName, cacheName);
+        freeAuxiliaryCache(key);
+    }
+
+    /**
+     * Dispose a cache and remove it from the map of registered auxiliary caches
+     *
+     * @param key the key into the map of auxiliaries
+     * @throws IOException if disposing of the cache fails
+     */
+    public void freeAuxiliaryCache(String key) throws IOException
+    {
+        AuxiliaryCache<?, ?> aux = auxiliaryCaches.remove( key );
+
+        if ( aux != null )
+        {
+            aux.dispose();
+        }
+    }
+
+    /**
+     * Gets stats for debugging. This calls gets statistics and then puts all the results in a
+     * string. This returns data for all regions.
+     * <p>
+     * @return String
+     */
+    @Override
+    public String getStats()
+    {
+        ICacheStats[] stats = getStatistics();
+        if ( stats == null )
+        {
+            return "NONE";
+        }
+
+        // force the array elements into a string.
+        StringBuilder buf = new StringBuilder();
+        Arrays.stream(stats).forEach(stat -> {
+            buf.append( "\n---------------------------\n" );
+            buf.append( stat );
+        });
+        return buf.toString();
+    }
+
+    /**
+     * This returns data gathered for all regions and all the auxiliaries they currently uses.
+     * <p>
+     * @return ICacheStats[]
+     */
+    public ICacheStats[] getStatistics()
+    {
+        List<ICacheStats> cacheStats = caches.values().stream()
+            .filter(cache -> cache != null)
+            .map(cache -> ((CompositeCache<?, ?>)cache).getStatistics() )
+            .collect(Collectors.toList());
+
+        return cacheStats.toArray( new CacheStats[0] );
+    }
+
+    /**
+     * Perhaps the composite cache itself should be the observable object. It doesn't make much of a
+     * difference. There are some problems with region by region shutdown. Some auxiliaries are
+     * global. They will need to track when every region has shutdown before doing things like
+     * closing the socket with a lateral.
+     * <p>
+     * @param observer
+     */
+    @Override
+    public void registerShutdownObserver( IShutdownObserver observer )
+    {
+    	if (!shutdownObservers.contains(observer))
+    	{
+    		shutdownObservers.push( observer );
+    	}
+    	else
+    	{
+    		log.warn("Shutdown observer added twice {0}", observer);
+    	}
+    }
+
+    /**
+     * @param observer
+     */
+    @Override
+    public void deregisterShutdownObserver( IShutdownObserver observer )
+    {
+        shutdownObservers.remove( observer );
+    }
+
+    /**
+     * This is exposed so other manager can get access to the props.
+     * <p>
+     * @return the configurationProperties
+     */
+    @Override
+    public Properties getConfigurationProperties()
+    {
+        return configurationProperties;
+    }
+
+    /**
+     * @return the isInitialized
+     */
+    public boolean isInitialized()
+    {
+        return isInitialized;
+    }
+
+    /**
+     * @return the isConfigured
+     */
+    public boolean isConfigured()
+    {
+        return isConfigured;
+    }
+
+    public void setJmxName(final String name)
+    {
+        if (isJMXRegistered)
+        {
+            throw new IllegalStateException("Too late, MBean registration is done");
+        }
+        jmxName = name;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/event/ElementEvent.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/event/ElementEvent.java
new file mode 100644
index 0000000..a53d985
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/event/ElementEvent.java
@@ -0,0 +1,74 @@
+package org.apache.commons.jcs3.engine.control.event;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.EventObject;
+
+import org.apache.commons.jcs3.engine.control.event.behavior.ElementEventType;
+import org.apache.commons.jcs3.engine.control.event.behavior.IElementEvent;
+
+/**
+ * Element events will trigger the creation of Element Event objects. This is a wrapper around the
+ * cache element that indicates the event triggered.
+ */
+public class ElementEvent<T>
+    extends EventObject
+    implements IElementEvent<T>
+{
+    /** Don't change */
+    private static final long serialVersionUID = -5364117411457467056L;
+
+    /** default event code */
+    private ElementEventType elementEvent = ElementEventType.EXCEEDED_MAXLIFE_BACKGROUND;
+
+    /**
+     * Constructor for the ElementEvent object
+     * <p>
+     * @param source The Cache Element
+     * @param elementEvent The event id defined in the enum class.
+     */
+    public ElementEvent( T source, ElementEventType elementEvent )
+    {
+        super( source );
+        this.elementEvent = elementEvent;
+    }
+
+    /**
+     * Gets the elementEvent attribute of the ElementEvent object
+     * <p>
+     * @return The elementEvent value. The List of values is defined in ElementEventType.
+     */
+    @Override
+    public ElementEventType getElementEvent()
+    {
+        return elementEvent;
+    }
+
+    /**
+     * @return the source of the event.
+     */
+    @SuppressWarnings("unchecked") // Generified
+    @Override
+    public T getSource()
+    {
+        return (T) super.getSource();
+
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/event/ElementEventQueue.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/event/ElementEventQueue.java
new file mode 100644
index 0000000..fdf2fa5
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/event/ElementEventQueue.java
@@ -0,0 +1,184 @@
+package org.apache.commons.jcs3.engine.control.event;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.concurrent.ExecutorService;
+
+import org.apache.commons.jcs3.engine.control.event.behavior.IElementEvent;
+import org.apache.commons.jcs3.engine.control.event.behavior.IElementEventHandler;
+import org.apache.commons.jcs3.engine.control.event.behavior.IElementEventQueue;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.threadpool.PoolConfiguration;
+import org.apache.commons.jcs3.utils.threadpool.ThreadPoolManager;
+import org.apache.commons.jcs3.utils.threadpool.PoolConfiguration.WhenBlockedPolicy;
+
+/**
+ * An event queue is used to propagate ordered cache events to one and only one target listener.
+ */
+public class ElementEventQueue
+    implements IElementEventQueue
+{
+    private static final String THREAD_PREFIX = "JCS-ElementEventQueue-";
+
+    /** The logger */
+    private static final Log log = LogManager.getLog( ElementEventQueue.class );
+
+    /** shutdown or not */
+    private boolean destroyed = false;
+
+    /** The worker thread pool. */
+    private ExecutorService queueProcessor;
+
+    /**
+     * Constructor for the ElementEventQueue object
+     */
+    public ElementEventQueue()
+    {
+        queueProcessor = ThreadPoolManager.getInstance().createPool(
+        		new PoolConfiguration(false, 0, 1, 1, 0, WhenBlockedPolicy.RUN, 1), THREAD_PREFIX);
+
+        log.debug( "Constructed: {0}", this );
+    }
+
+    /**
+     * Dispose queue
+     */
+    @Override
+    public void dispose()
+    {
+        if ( !destroyed )
+        {
+            destroyed = true;
+
+            // synchronize on queue so the thread will not wait forever,
+            // and then interrupt the QueueProcessor
+            queueProcessor.shutdownNow();
+            queueProcessor = null;
+
+            log.info( "Element event queue destroyed: {0}", this );
+        }
+    }
+
+    /**
+     * Adds an ElementEvent to be handled
+     * @param hand The IElementEventHandler
+     * @param event The IElementEventHandler IElementEvent event
+     * @throws IOException
+     */
+    @Override
+    public <T> void addElementEvent( IElementEventHandler hand, IElementEvent<T> event )
+        throws IOException
+    {
+
+        log.debug( "Adding Event Handler to QUEUE, !destroyed = {0}", !destroyed );
+
+        if (destroyed)
+        {
+            log.warn("Event submitted to disposed element event queue {0}", event);
+        }
+        else
+        {
+            ElementEventRunner runner = new ElementEventRunner( hand, event );
+
+            log.debug( "runner = {0}", runner );
+
+            queueProcessor.execute(runner);
+        }
+    }
+
+    // /////////////////////////// Inner classes /////////////////////////////
+
+    /**
+     * Retries before declaring failure.
+     */
+    protected abstract class AbstractElementEventRunner
+        implements Runnable
+    {
+        /**
+         * Main processing method for the AbstractElementEvent object
+         */
+        @SuppressWarnings("synthetic-access")
+        @Override
+        public void run()
+        {
+            try
+            {
+                doRun();
+                // happy and done.
+            }
+            catch ( IOException e )
+            {
+                // Too bad. The handler has problems.
+                log.warn( "Giving up element event handling {0}", ElementEventQueue.this, e );
+            }
+        }
+
+        /**
+         * This will do the work or trigger the work to be done.
+         * <p>
+         * @throws IOException
+         */
+        protected abstract void doRun()
+            throws IOException;
+    }
+
+    /**
+     * ElementEventRunner.
+     */
+    private class ElementEventRunner
+        extends AbstractElementEventRunner
+    {
+        /** the handler */
+        private final IElementEventHandler hand;
+
+        /** event */
+        private final IElementEvent<?> event;
+
+        /**
+         * Constructor for the PutEvent object.
+         * <p>
+         * @param hand
+         * @param event
+         * @throws IOException
+         */
+        @SuppressWarnings("synthetic-access")
+        ElementEventRunner( IElementEventHandler hand, IElementEvent<?> event )
+            throws IOException
+        {
+            log.debug( "Constructing {0}", this );
+            this.hand = hand;
+            this.event = event;
+        }
+
+        /**
+         * Tells the handler to handle the event.
+         * <p>
+         * @throws IOException
+         */
+        @Override
+        protected void doRun()
+            throws IOException
+        {
+            hand.handleElementEvent( event );
+        }
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/event/behavior/ElementEventType.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/event/behavior/ElementEventType.java
new file mode 100644
index 0000000..69b32f4
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/event/behavior/ElementEventType.java
@@ -0,0 +1,54 @@
+package org.apache.commons.jcs3.engine.control.event.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * This describes the events that an item can encounter.
+ */
+public enum ElementEventType
+{
+    /** Background expiration */
+    EXCEEDED_MAXLIFE_BACKGROUND,
+
+    /*** Expiration discovered on request */
+    EXCEEDED_MAXLIFE_ONREQUEST,
+
+    /** Background expiration */
+    EXCEEDED_IDLETIME_BACKGROUND,
+
+    /** Expiration discovered on request */
+    EXCEEDED_IDLETIME_ONREQUEST,
+
+    /** Moving from memory to disk (what if no disk?) */
+    SPOOLED_DISK_AVAILABLE,
+
+    /** Moving from memory to disk (what if no disk?) */
+    SPOOLED_DISK_NOT_AVAILABLE,
+
+    /** Moving from memory to disk, but item is not spoolable */
+    SPOOLED_NOT_ALLOWED //,
+
+    /** Removed actively by a remove command. (Could distinguish between local and remote) */
+    //REMOVED,
+    /**
+     * Element was requested from cache. Not sure we ever want to implement this.
+     */
+    //GET
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/event/behavior/IElementEvent.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/event/behavior/IElementEvent.java
new file mode 100644
index 0000000..8dde606
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/event/behavior/IElementEvent.java
@@ -0,0 +1,42 @@
+package org.apache.commons.jcs3.engine.control.event.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.Serializable;
+
+/**
+ * Defines how an element event object should behave.
+ */
+public interface IElementEvent<T>
+    extends Serializable
+{
+    /**
+     * Gets the elementEvent attribute of the IElementEvent object. This code is Contained in the
+     * IElememtEventConstants class.
+     *<p>
+     * @return The elementEvent value
+     */
+    ElementEventType getElementEvent();
+
+    /**
+     * @return the source of the event.
+     */
+    T getSource();
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/event/behavior/IElementEventHandler.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/event/behavior/IElementEventHandler.java
new file mode 100644
index 0000000..00d426b
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/event/behavior/IElementEventHandler.java
@@ -0,0 +1,40 @@
+package org.apache.commons.jcs3.engine.control.event.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * This interface defines the behavior for event handler. Event handlers are
+ * transient. They are not replicated and are not written to disk.
+ * <p>
+ * If you want an event handler by default for all elements in a region, then
+ * you can add it to the default element attributes. This way it will get created
+ * whenever an item gets put into the cache.
+ *
+ */
+public interface IElementEventHandler
+{
+    /**
+     * Handle events for this element. The events are typed.
+     *
+     * @param event
+     *            The event created by the cache.
+     */
+    <T> void handleElementEvent( IElementEvent<T> event );
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/event/behavior/IElementEventQueue.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/event/behavior/IElementEventQueue.java
new file mode 100644
index 0000000..0a25011
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/event/behavior/IElementEventQueue.java
@@ -0,0 +1,48 @@
+package org.apache.commons.jcs3.engine.control.event.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+
+/**
+ * Interface for an element event queue. An event queue is used to propagate
+ * ordered element events in one region.
+ *
+ */
+public interface IElementEventQueue
+{
+    /**
+     * Adds an ElementEvent to be handled
+     *
+     * @param hand
+     *            The IElementEventHandler
+     * @param event
+     *            The IElementEventHandler IElementEvent event
+     * @throws IOException
+     */
+    <T> void addElementEvent( IElementEventHandler hand, IElementEvent<T> event )
+        throws IOException;
+
+    /**
+     * Destroy the event queue
+     *
+     */
+    void dispose();
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/group/GroupAttrName.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/group/GroupAttrName.java
new file mode 100644
index 0000000..9b69faf
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/group/GroupAttrName.java
@@ -0,0 +1,117 @@
+package org.apache.commons.jcs3.engine.control.group;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.Serializable;
+
+/**
+ * Description of the Class
+ */
+public class GroupAttrName<T>
+    implements Serializable
+{
+    /** Don't change */
+    private static final long serialVersionUID = 1586079686300744198L;
+
+    /** Description of the Field */
+    public final GroupId groupId;
+
+    /** the name of the attribute */
+    public final T attrName;
+
+    /** Cached toString value */
+    private String toString;
+
+    /**
+     * Constructor for the GroupAttrName object
+     * @param groupId
+     * @param attrName
+     */
+    public GroupAttrName( GroupId groupId, T attrName )
+    {
+        this.groupId = groupId;
+        this.attrName = attrName;
+
+        if ( groupId == null )
+        {
+            throw new IllegalArgumentException( "groupId must not be null." );
+        }
+    }
+
+    /**
+     * Tests object equality.
+     * @param obj The <code>GroupAttrName</code> instance to test.
+     * @return Whether equal.
+     */
+    @Override
+    public boolean equals( Object obj )
+    {
+        if ( obj == null || !( obj instanceof GroupAttrName ) )
+        {
+            return false;
+        }
+        GroupAttrName<?> to = (GroupAttrName<?>) obj;
+
+        if (groupId.equals( to.groupId ))
+        {
+            if (attrName == null && to.attrName == null)
+            {
+                return true;
+            }
+            else if (attrName == null || to.attrName == null)
+            {
+                return false;
+            }
+
+            return  attrName.equals( to.attrName );
+        }
+
+        return false;
+    }
+
+    /**
+     * @return A hash code based on the hash code of @ #groupid} and {@link #attrName}.
+     */
+    @Override
+    public int hashCode()
+    {
+        if (attrName == null)
+        {
+            return groupId.hashCode();
+        }
+
+        return groupId.hashCode() ^ attrName.hashCode();
+    }
+
+    /**
+     * @return the cached value.
+     */
+    @Override
+    public String toString()
+    {
+        if ( toString == null )
+        {
+            toString = "[GAN: groupId=" + groupId + ", attrName=" + attrName + "]";
+        }
+
+        return toString;
+    }
+
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/group/GroupId.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/group/GroupId.java
new file mode 100644
index 0000000..7decea7
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/group/GroupId.java
@@ -0,0 +1,103 @@
+package org.apache.commons.jcs3.engine.control.group;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.Serializable;
+
+/**
+ * Used to avoid name conflict when group cache items are mixed with non-group cache items in the
+ * same cache.
+ */
+public class GroupId
+    implements Serializable
+{
+    /** Don't change. */
+    private static final long serialVersionUID = 4626368486444860133L;
+
+    /** Description of the Field */
+    public final String groupName;
+
+    /** the name of the region. */
+    public final String cacheName;
+
+    /** Cached toString value. */
+    private String toString;
+
+    /**
+     * Constructor for the GroupId object
+     * <p>
+     * @param cacheName
+     * @param groupName
+     */
+    public GroupId( String cacheName, String groupName )
+    {
+        this.cacheName = cacheName;
+        this.groupName = groupName;
+
+        if ( cacheName == null )
+        {
+            throw new IllegalArgumentException( "cacheName must not be null." );
+        }
+        if ( groupName == null )
+        {
+            throw new IllegalArgumentException( "groupName must not be null." );
+        }
+    }
+
+    /**
+     * @param obj
+     * @return cacheName.equals( g.cacheName ) &amp;&amp;groupName.equals( g.groupName );
+     */
+    @Override
+    public boolean equals( Object obj )
+    {
+        if ( obj == null || !( obj instanceof GroupId ) )
+        {
+            return false;
+        }
+        GroupId g = (GroupId) obj;
+        return cacheName.equals( g.cacheName ) && groupName.equals( g.groupName );
+    }
+
+    /**
+     * @return cacheName.hashCode() + groupName.hashCode();
+     */
+    @Override
+    public int hashCode()
+    {
+        return cacheName.hashCode() + groupName.hashCode();
+    }
+
+    /**
+     * Caches the value.
+     * <p>
+     * @return debugging string.
+     */
+    @Override
+    public String toString()
+    {
+        if ( toString == null )
+        {
+            toString = "[groupId=" + cacheName + ", " + groupName + ']';
+        }
+
+        return toString;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/package.html b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/package.html
similarity index 100%
rename from commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/control/package.html
rename to commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/package.html
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/logging/CacheEvent.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/logging/CacheEvent.java
new file mode 100644
index 0000000..16d011a
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/logging/CacheEvent.java
@@ -0,0 +1,177 @@
+package org.apache.commons.jcs3.engine.logging;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Date;
+
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEvent;
+
+/** It's returned from create and passed into log. */
+public class CacheEvent<K>
+    implements ICacheEvent<K>
+{
+    /** Don't change. */
+    private static final long serialVersionUID = -5913139566421714330L;
+
+    /** The time at which this object was created. */
+    private final long createTime = System.currentTimeMillis();
+
+    /** The auxiliary or other source of the event. */
+    private String source;
+
+    /** The cache region */
+    private String region;
+
+    /** The event name: update, get, remove, etc. */
+    private String eventName;
+
+    /** disk location, ip, etc. */
+    private String optionalDetails;
+
+    /** The key that was put or retrieved. */
+    private K key;
+
+    /**
+     * @param source the source to set
+     */
+    @Override
+	public void setSource( String source )
+    {
+        this.source = source;
+    }
+
+    /**
+     * @return the source
+     */
+    @Override
+	public String getSource()
+    {
+        return source;
+    }
+
+    /**
+     * @param region the region to set
+     */
+    @Override
+	public void setRegion( String region )
+    {
+        this.region = region;
+    }
+
+    /**
+     * @return the region
+     */
+    @Override
+	public String getRegion()
+    {
+        return region;
+    }
+
+    /**
+     * @param eventName the eventName to set
+     */
+    @Override
+	public void setEventName( String eventName )
+    {
+        this.eventName = eventName;
+    }
+
+    /**
+     * @return the eventName
+     */
+    @Override
+	public String getEventName()
+    {
+        return eventName;
+    }
+
+    /**
+     * @param optionalDetails the optionalDetails to set
+     */
+    @Override
+	public void setOptionalDetails( String optionalDetails )
+    {
+        this.optionalDetails = optionalDetails;
+    }
+
+    /**
+     * @return the optionalDetails
+     */
+    @Override
+	public String getOptionalDetails()
+    {
+        return optionalDetails;
+    }
+
+    /**
+     * @param key the key to set
+     */
+    @Override
+	public void setKey( K key )
+    {
+        this.key = key;
+    }
+
+    /**
+     * @return the key
+     */
+    @Override
+	public K getKey()
+    {
+        return key;
+    }
+
+    /**
+     * The time at which this object was created.
+     * <p>
+     * @return the createTime
+     */
+    public long getCreateTime()
+    {
+        return createTime;
+    }
+
+    /**
+     * @return reflection toString
+     */
+    @Override
+    public String toString()
+    {
+    	StringBuilder sb = new StringBuilder();
+    	sb.append("CacheEvent: ").append(eventName).append(" Created: ").append(new Date(createTime));
+    	if (source != null)
+    	{
+        	sb.append(" Source: ").append(source);
+    	}
+    	if (region != null)
+    	{
+        	sb.append(" Region: ").append(region);
+    	}
+    	if (key != null)
+    	{
+        	sb.append(" Key: ").append(key);
+    	}
+    	if (optionalDetails != null)
+    	{
+        	sb.append(" Details: ").append(optionalDetails);
+    	}
+        return sb.toString();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/logging/CacheEventLoggerDebugLogger.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/logging/CacheEventLoggerDebugLogger.java
new file mode 100644
index 0000000..bae2694
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/logging/CacheEventLoggerDebugLogger.java
@@ -0,0 +1,104 @@
+package org.apache.commons.jcs3.engine.logging;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEvent;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * This implementation simple logs to a logger at debug level, for all events. It's mainly
+ * for testing. It isn't very useful otherwise.
+ */
+public class CacheEventLoggerDebugLogger
+    implements ICacheEventLogger
+{
+    /** This is the name of the category. */
+    private String logCategoryName = CacheEventLoggerDebugLogger.class.getName();
+
+    /** The logger. This is recreated on set logCategoryName */
+    private Log log = LogManager.getLog( logCategoryName );
+
+    /**
+     * @param source
+     * @param region
+     * @param eventName
+     * @param optionalDetails
+     * @param key
+     * @return ICacheEvent
+     */
+    @Override
+    public <T> ICacheEvent<T> createICacheEvent( String source, String region, String eventName,
+            String optionalDetails, T key )
+    {
+        ICacheEvent<T> event = new CacheEvent<>();
+        event.setSource( source );
+        event.setRegion( region );
+        event.setEventName( eventName );
+        event.setOptionalDetails( optionalDetails );
+        event.setKey( key );
+
+        return event;
+    }
+
+    /**
+     * @param source
+     * @param eventName
+     * @param optionalDetails
+     */
+    @Override
+    public void logApplicationEvent( String source, String eventName, String optionalDetails )
+    {
+        log.debug( "{0} | {1} | {2}", source, eventName, optionalDetails );
+    }
+
+    /**
+     * @param source
+     * @param eventName
+     * @param errorMessage
+     */
+    @Override
+    public void logError( String source, String eventName, String errorMessage )
+    {
+        log.debug( "{0} | {1} | {2}", source, eventName, errorMessage );
+    }
+
+    /**
+     * @param event
+     */
+    @Override
+    public <T> void logICacheEvent( ICacheEvent<T> event )
+    {
+        log.debug( event );
+    }
+
+    /**
+     * @param logCategoryName
+     */
+    public synchronized void setLogCategoryName( String logCategoryName )
+    {
+        if ( logCategoryName != null && !logCategoryName.equals( this.logCategoryName ) )
+        {
+            this.logCategoryName = logCategoryName;
+            log = LogManager.getLog( logCategoryName );
+        }
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/logging/behavior/ICacheEvent.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/logging/behavior/ICacheEvent.java
new file mode 100644
index 0000000..0a9b018
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/logging/behavior/ICacheEvent.java
@@ -0,0 +1,77 @@
+package org.apache.commons.jcs3.engine.logging.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.Serializable;
+
+/** Defines the common fields required by a cache event. */
+public interface ICacheEvent<K>
+    extends Serializable
+{
+    /**
+     * @param source the source to set
+     */
+    void setSource( String source );
+
+    /**
+     * @return the source
+     */
+    String getSource();
+
+    /**
+     * @param region the region to set
+     */
+    void setRegion( String region );
+
+    /**
+     * @return the region
+     */
+    String getRegion();
+
+    /**
+     * @param eventName the eventName to set
+     */
+    void setEventName( String eventName );
+
+    /**
+     * @return the eventName
+     */
+    String getEventName();
+
+    /**
+     * @param optionalDetails the optionalDetails to set
+     */
+    void setOptionalDetails( String optionalDetails );
+
+    /**
+     * @return the optionalDetails
+     */
+    String getOptionalDetails();
+
+    /**
+     * @param key the key to set
+     */
+    void setKey( K key );
+
+    /**
+     * @return the key
+     */
+    K getKey();
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/logging/behavior/ICacheEventLogger.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/logging/behavior/ICacheEventLogger.java
new file mode 100644
index 0000000..2a798f9
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/logging/behavior/ICacheEventLogger.java
@@ -0,0 +1,92 @@
+package org.apache.commons.jcs3.engine.logging.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * This defines the behavior for event logging. Auxiliaries will send events to injected event
+ * loggers.
+ * <p>
+ * In general all ICache interface methods should call the logger if one is configured. This will be
+ * done on an ad hoc basis for now. Various auxiliaries may have additional events.
+ */
+public interface ICacheEventLogger
+{
+	// TODO: Use enum
+    /** ICache update */
+    String UPDATE_EVENT = "update";
+
+    /** ICache get */
+    String GET_EVENT = "get";
+
+    /** ICache getMultiple */
+    String GETMULTIPLE_EVENT = "getMultiple";
+
+    /** ICache getMatching */
+    String GETMATCHING_EVENT = "getMatching";
+
+    /** ICache remove */
+    String REMOVE_EVENT = "remove";
+
+    /** ICache removeAll */
+    String REMOVEALL_EVENT = "removeAll";
+
+    /** ICache dispose */
+    String DISPOSE_EVENT = "dispose";
+
+    /** ICache enqueue. The time in the queue. */
+    //String ENQUEUE_EVENT = "enqueue";
+    /**
+     * Creates an event.
+     * <p>
+     * @param source - e.g. RemoteCacheServer
+     * @param region - the name of the region
+     * @param eventName - e.g. update, get, put, remove
+     * @param optionalDetails - any extra message
+     * @param key - the cache key
+     * @return ICacheEvent
+     */
+    <T> ICacheEvent<T> createICacheEvent( String source, String region,
+            String eventName, String optionalDetails, T key );
+
+    /**
+     * Logs an event.
+     * <p>
+     * @param event - the event created in createICacheEvent
+     */
+    <T> void logICacheEvent( ICacheEvent<T> event );
+
+    /**
+     * Logs an event. These are internal application events that do not correspond to ICache calls.
+     * <p>
+     * @param source - e.g. RemoteCacheServer
+     * @param eventName - e.g. update, get, put, remove
+     * @param optionalDetails - any extra message
+     */
+    void logApplicationEvent( String source, String eventName, String optionalDetails );
+
+    /**
+     * Logs an error.
+     * <p>
+     * @param source - e.g. RemoteCacheServer
+     * @param eventName - e.g. update, get, put, remove
+     * @param errorMessage - any error message
+     */
+    void logError( String source, String eventName, String errorMessage );
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/match/KeyMatcherPatternImpl.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/match/KeyMatcherPatternImpl.java
new file mode 100644
index 0000000..33bdb16
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/match/KeyMatcherPatternImpl.java
@@ -0,0 +1,51 @@
+package org.apache.commons.jcs3.engine.match;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import org.apache.commons.jcs3.engine.match.behavior.IKeyMatcher;
+
+/** This implementation of the KeyMatcher uses standard Java Pattern matching. */
+public class KeyMatcherPatternImpl<K>
+    implements IKeyMatcher<K>
+{
+    /** Serial version */
+    private static final long serialVersionUID = 6667352064144381264L;
+
+    /**
+     * Creates a pattern and find matches on the array.
+     * <p>
+     * @param pattern
+     * @param keyArray
+     * @return Set of the matching keys
+     */
+    @Override
+    public Set<K> getMatchingKeysFromArray( String pattern, Set<K> keyArray )
+    {
+        Pattern compiledPattern = Pattern.compile( pattern );
+
+        return keyArray.stream()
+                .filter(key -> compiledPattern.matcher(key.toString()).matches())
+                .collect(Collectors.toSet());
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/match/behavior/IKeyMatcher.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/match/behavior/IKeyMatcher.java
new file mode 100644
index 0000000..34619d1
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/match/behavior/IKeyMatcher.java
@@ -0,0 +1,36 @@
+package org.apache.commons.jcs3.engine.match.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.Serializable;
+import java.util.Set;
+
+/** Key matchers need to implement this interface. */
+public interface IKeyMatcher<K> extends Serializable
+{
+    /**
+     * Creates a pattern and find matches on the array.
+     * <p>
+     * @param pattern
+     * @param keyArray
+     * @return Set of the matching keys
+     */
+    Set<K> getMatchingKeysFromArray( String pattern, Set<K> keyArray );
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/AbstractDoubleLinkedListMemoryCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/AbstractDoubleLinkedListMemoryCache.java
new file mode 100644
index 0000000..55479c5
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/AbstractDoubleLinkedListMemoryCache.java
@@ -0,0 +1,526 @@
+package org.apache.commons.jcs3.engine.memory;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+import org.apache.commons.jcs3.engine.control.group.GroupAttrName;
+import org.apache.commons.jcs3.engine.memory.util.MemoryElementDescriptor;
+import org.apache.commons.jcs3.engine.stats.StatElement;
+import org.apache.commons.jcs3.engine.stats.behavior.IStatElement;
+import org.apache.commons.jcs3.engine.stats.behavior.IStats;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.struct.DoubleLinkedList;
+
+/**
+ * This class contains methods that are common to memory caches using the double linked list, such
+ * as the LRU, MRU, FIFO, and LIFO caches.
+ * <p>
+ * Children can control the expiration algorithm by controlling the update and get. The last item in the list will be the one
+ * removed when the list fills. For instance LRU should more items to the front as they are used. FIFO should simply add new items
+ * to the front of the list.
+ */
+public abstract class AbstractDoubleLinkedListMemoryCache<K, V> extends AbstractMemoryCache<K, V>
+{
+    /** The logger. */
+    private static final Log log = LogManager.getLog(AbstractDoubleLinkedListMemoryCache.class);
+
+    /** thread-safe double linked list for lru */
+    protected DoubleLinkedList<MemoryElementDescriptor<K, V>> list; // TODO privatise
+
+    /**
+     * For post reflection creation initialization.
+     * <p>
+     *
+     * @param hub
+     */
+    @Override
+    public void initialize(CompositeCache<K, V> hub)
+    {
+        super.initialize(hub);
+        list = new DoubleLinkedList<>();
+        log.info("initialized MemoryCache for {0}", () -> getCacheName());
+    }
+
+    /**
+     * This is called by super initialize.
+     *
+     * NOTE: should return a thread safe map
+     *
+     * <p>
+     *
+     * @return new ConcurrentHashMap()
+     */
+    @Override
+    public ConcurrentMap<K, MemoryElementDescriptor<K, V>> createMap()
+    {
+        return new ConcurrentHashMap<>();
+    }
+
+    /**
+     * Calls the abstract method updateList.
+     * <p>
+     * If the max size is reached, an element will be put to disk.
+     * <p>
+     *
+     * @param ce
+     *            The cache element, or entry wrapper
+     * @throws IOException
+     */
+    @Override
+    public final void update(ICacheElement<K, V> ce) throws IOException
+    {
+        putCnt.incrementAndGet();
+
+        lock.lock();
+        try
+        {
+            MemoryElementDescriptor<K, V> newNode = adjustListForUpdate(ce);
+
+            // this should be synchronized if we were not using a ConcurrentHashMap
+            final K key = newNode.getCacheElement().getKey();
+            MemoryElementDescriptor<K, V> oldNode = map.put(key, newNode);
+
+            // If the node was the same as an existing node, remove it.
+            if (oldNode != null && key.equals(oldNode.getCacheElement().getKey()))
+            {
+                list.remove(oldNode);
+            }
+        }
+        finally
+        {
+            lock.unlock();
+        }
+
+        // If we are over the max spool some
+        spoolIfNeeded();
+    }
+
+    /**
+     * Children implement this to control the cache expiration algorithm
+     * <p>
+     *
+     * @param ce
+     * @return MemoryElementDescriptor the new node
+     * @throws IOException
+     */
+    protected abstract MemoryElementDescriptor<K, V> adjustListForUpdate(ICacheElement<K, V> ce) throws IOException;
+
+    /**
+     * If the max size has been reached, spool.
+     * <p>
+     *
+     * @throws Error
+     */
+    private void spoolIfNeeded() throws Error
+    {
+        int size = map.size();
+        // If the element limit is reached, we need to spool
+
+        if (size <= this.getCacheAttributes().getMaxObjects())
+        {
+            return;
+        }
+
+        log.debug("In memory limit reached, spooling");
+
+        // Write the last 'chunkSize' items to disk.
+        int chunkSizeCorrected = Math.min(size, chunkSize);
+
+        log.debug("About to spool to disk cache, map size: {0}, max objects: {1}, "
+                + "maximum items to spool: {2}", () -> size,
+                () -> this.getCacheAttributes().getMaxObjects(),
+                () -> chunkSizeCorrected);
+
+        // The spool will put them in a disk event queue, so there is no
+        // need to pre-queue the queuing. This would be a bit wasteful
+        // and wouldn't save much time in this synchronous call.
+        lock.lock();
+
+        try
+        {
+            for (int i = 0; i < chunkSizeCorrected; i++)
+            {
+                ICacheElement<K, V> lastElement = spoolLastElement();
+                if (lastElement == null)
+                {
+                    break;
+                }
+            }
+
+            // If this is out of the sync block it can detect a mismatch
+            // where there is none.
+            if (log.isDebugEnabled() && map.size() != list.size())
+            {
+                log.debug("update: After spool, size mismatch: map.size() = {0}, "
+                        + "linked list size = {1}", map.size(), list.size());
+            }
+        }
+        finally
+        {
+            lock.unlock();
+        }
+
+        log.debug("update: After spool map size: {0} linked list size = {1}",
+                () -> map.size(), () -> list.size());
+    }
+
+    /**
+     * This instructs the memory cache to remove the <i>numberToFree</i> according to its eviction
+     * policy. For example, the LRUMemoryCache will remove the <i>numberToFree</i> least recently
+     * used items. These will be spooled to disk if a disk auxiliary is available.
+     * <p>
+     *
+     * @param numberToFree
+     * @return the number that were removed. if you ask to free 5, but there are only 3, you will
+     *         get 3.
+     * @throws IOException
+     */
+    @Override
+    public int freeElements(int numberToFree) throws IOException
+    {
+        int freed = 0;
+
+        lock.lock();
+
+        try
+        {
+            for (; freed < numberToFree; freed++)
+            {
+                ICacheElement<K, V> element = spoolLastElement();
+                if (element == null)
+                {
+                    break;
+                }
+            }
+        }
+        finally
+        {
+            lock.unlock();
+        }
+
+        return freed;
+    }
+
+    /**
+     * This spools the last element in the LRU, if one exists.
+     * <p>
+     *
+     * @return ICacheElement&lt;K, V&gt; if there was a last element, else null.
+     * @throws Error
+     */
+    private ICacheElement<K, V> spoolLastElement() throws Error
+    {
+        ICacheElement<K, V> toSpool = null;
+
+        final MemoryElementDescriptor<K, V> last = list.getLast();
+        if (last != null)
+        {
+            toSpool = last.getCacheElement();
+            if (toSpool != null)
+            {
+                getCompositeCache().spoolToDisk(toSpool);
+                if (map.remove(toSpool.getKey()) == null)
+                {
+                    log.warn("update: remove failed for key: {0}", toSpool.getKey());
+
+                    if (log.isTraceEnabled())
+                    {
+                        verifyCache();
+                    }
+                }
+            }
+            else
+            {
+                throw new Error("update: last.ce is null!");
+            }
+
+            list.remove(last);
+        }
+
+        return toSpool;
+    }
+
+    /**
+     * @see org.apache.commons.jcs3.engine.memory.AbstractMemoryCache#get(java.lang.Object)
+     */
+    @Override
+    public ICacheElement<K, V> get(K key) throws IOException
+    {
+        ICacheElement<K, V> ce = super.get(key);
+
+        if (log.isTraceEnabled())
+        {
+            verifyCache();
+        }
+
+        return ce;
+    }
+
+    /**
+     * Adjust the list as needed for a get. This allows children to control the algorithm
+     * <p>
+     *
+     * @param me
+     */
+    protected abstract void adjustListForGet(MemoryElementDescriptor<K, V> me);
+
+    /**
+     * Update control structures after get
+     * (guarded by the lock)
+     *
+     * @param me the memory element descriptor
+     */
+    @Override
+    protected void lockedGetElement(MemoryElementDescriptor<K, V> me)
+    {
+        adjustListForGet(me);
+    }
+
+    /**
+     * Remove element from control structure
+     * (guarded by the lock)
+     *
+     * @param me the memory element descriptor
+     */
+    @Override
+    protected void lockedRemoveElement(MemoryElementDescriptor<K, V> me)
+    {
+        list.remove(me);
+    }
+
+    /**
+     * Removes all cached items from the cache control structures.
+     * (guarded by the lock)
+     */
+    @Override
+    protected void lockedRemoveAll()
+    {
+        list.removeAll();
+    }
+
+    // --------------------------- internal methods (linked list implementation)
+    /**
+     * Adds a new node to the start of the link list.
+     * <p>
+     *
+     * @param ce
+     *            The feature to be added to the First
+     * @return MemoryElementDescriptor
+     */
+    protected MemoryElementDescriptor<K, V> addFirst(ICacheElement<K, V> ce)
+    {
+        lock.lock();
+        try
+        {
+            MemoryElementDescriptor<K, V> me = new MemoryElementDescriptor<>(ce);
+            list.addFirst(me);
+            if ( log.isTraceEnabled() )
+            {
+                verifyCache(ce.getKey());
+            }
+            return me;
+        }
+        finally
+        {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Adds a new node to the end of the link list.
+     * <p>
+     *
+     * @param ce
+     *            The feature to be added to the First
+     * @return MemoryElementDescriptor
+     */
+    protected MemoryElementDescriptor<K, V> addLast(ICacheElement<K, V> ce)
+    {
+        lock.lock();
+        try
+        {
+            MemoryElementDescriptor<K, V> me = new MemoryElementDescriptor<>(ce);
+            list.addLast(me);
+            if ( log.isTraceEnabled() )
+            {
+                verifyCache(ce.getKey());
+            }
+            return me;
+        }
+        finally
+        {
+            lock.unlock();
+        }
+    }
+
+    // ---------------------------------------------------------- debug methods
+
+    /**
+     * Dump the cache entries from first to list for debugging.
+     */
+    @SuppressWarnings("unchecked")
+    // No generics for public fields
+    private void dumpCacheEntries()
+    {
+        log.trace("dumpingCacheEntries");
+        for (MemoryElementDescriptor<K, V> me = list.getFirst(); me != null; me = (MemoryElementDescriptor<K, V>) me.next)
+        {
+            log.trace("dumpCacheEntries> key={0}, val={1}",
+                    me.getCacheElement().getKey(), me.getCacheElement().getVal());
+        }
+    }
+
+    /**
+     * Checks to see if all the items that should be in the cache are. Checks consistency between
+     * List and map.
+     */
+    @SuppressWarnings("unchecked")
+    // No generics for public fields
+    private void verifyCache()
+    {
+        boolean found = false;
+        log.trace("verifycache[{0}]: map contains {1} elements, linked list "
+                + "contains {2} elements", getCacheName(), map.size(),
+                list.size());
+        log.trace("verifycache: checking linked list by key ");
+        for (MemoryElementDescriptor<K, V> li = list.getFirst(); li != null; li = (MemoryElementDescriptor<K, V>) li.next)
+        {
+            K key = li.getCacheElement().getKey();
+            if (!map.containsKey(key))
+            {
+                log.error("verifycache[{0}]: map does not contain key : {1}",
+                        getCacheName(), key);
+                log.error("key class={0}", key.getClass());
+                log.error("key hashcode={0}", key.hashCode());
+                log.error("key toString={0}", key.toString());
+                if (key instanceof GroupAttrName)
+                {
+                    GroupAttrName<?> name = (GroupAttrName<?>) key;
+                    log.error("GroupID hashcode={0}", name.groupId.hashCode());
+                    log.error("GroupID.class={0}", name.groupId.getClass());
+                    log.error("AttrName hashcode={0}", name.attrName.hashCode());
+                    log.error("AttrName.class={0}", name.attrName.getClass());
+                }
+                dumpMap();
+            }
+            else if (map.get(key) == null)
+            {
+                log.error("verifycache[{0}]: linked list retrieval returned "
+                        + "null for key: {1}", getCacheName(), key);
+            }
+        }
+
+        log.trace("verifycache: checking linked list by value ");
+        for (MemoryElementDescriptor<K, V> li = list.getFirst(); li != null; li = (MemoryElementDescriptor<K, V>) li.next)
+        {
+            if (!map.containsValue(li))
+            {
+                log.error("verifycache[{0}]: map does not contain value: {1}",
+                        getCacheName(), li);
+                dumpMap();
+            }
+        }
+
+        log.trace("verifycache: checking via keysets!");
+        for (Object val : map.keySet())
+        {
+            found = false;
+
+            for (MemoryElementDescriptor<K, V> li = list.getFirst(); li != null; li = (MemoryElementDescriptor<K, V>) li.next)
+            {
+                if (val.equals(li.getCacheElement().getKey()))
+                {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found)
+            {
+                log.error("verifycache[{0}]: key not found in list : {1}",
+                        getCacheName(), val);
+                dumpCacheEntries();
+                if (map.containsKey(val))
+                {
+                    log.error("verifycache: map contains key");
+                }
+                else
+                {
+                    log.error("verifycache: map does NOT contain key, what the HECK!");
+                }
+            }
+        }
+    }
+
+    /**
+     * Logs an error if an element that should be in the cache is not.
+     * <p>
+     *
+     * @param key
+     */
+    @SuppressWarnings("unchecked")
+    // No generics for public fields
+    private void verifyCache(K key)
+    {
+        boolean found = false;
+
+        // go through the linked list looking for the key
+        for (MemoryElementDescriptor<K, V> li = list.getFirst(); li != null; li = (MemoryElementDescriptor<K, V>) li.next)
+        {
+            if (li.getCacheElement().getKey() == key)
+            {
+                found = true;
+                log.trace("verifycache(key) key match: {0}", key);
+                break;
+            }
+        }
+        if (!found)
+        {
+            log.error("verifycache(key)[{0}], couldn't find key! : {1}",
+                    getCacheName(), key);
+        }
+    }
+
+    /**
+     * This returns semi-structured information on the memory cache, such as the size, put count,
+     * hit count, and miss count.
+     * <p>
+     *
+     * @see org.apache.commons.jcs3.engine.memory.behavior.IMemoryCache#getStatistics()
+     */
+    @Override
+    public IStats getStatistics()
+    {
+        IStats stats = super.getStatistics();
+        stats.setTypeName( /* add algorithm name */"Memory Cache");
+
+        List<IStatElement<?>> elems = stats.getStatElements();
+
+        elems.add(new StatElement<>("List Size", Integer.valueOf(list.size())));
+
+        return stats;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/AbstractMemoryCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/AbstractMemoryCache.java
new file mode 100644
index 0000000..dec6227
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/AbstractMemoryCache.java
@@ -0,0 +1,513 @@
+package org.apache.commons.jcs3.engine.memory;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Collectors;
+
+import org.apache.commons.jcs3.engine.behavior.ICache;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+import org.apache.commons.jcs3.engine.control.group.GroupAttrName;
+import org.apache.commons.jcs3.engine.control.group.GroupId;
+import org.apache.commons.jcs3.engine.memory.behavior.IMemoryCache;
+import org.apache.commons.jcs3.engine.memory.util.MemoryElementDescriptor;
+import org.apache.commons.jcs3.engine.stats.StatElement;
+import org.apache.commons.jcs3.engine.stats.Stats;
+import org.apache.commons.jcs3.engine.stats.behavior.IStatElement;
+import org.apache.commons.jcs3.engine.stats.behavior.IStats;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * This base includes some common code for memory caches.
+ */
+public abstract class AbstractMemoryCache<K, V>
+    implements IMemoryCache<K, V>
+{
+    /** Log instance */
+    private static final Log log = LogManager.getLog( AbstractMemoryCache.class );
+
+    /** Cache Attributes.  Regions settings. */
+    private ICompositeCacheAttributes cacheAttributes;
+
+    /** The cache region this store is associated with */
+    private CompositeCache<K, V> cache;
+
+    /** How many to spool at a time. */
+    protected int chunkSize;
+
+    protected final Lock lock = new ReentrantLock();
+
+    /** Map where items are stored by key.  This is created by the concrete child class. */
+    protected Map<K, MemoryElementDescriptor<K, V>> map;// TODO privatise
+
+    /** number of hits */
+    protected AtomicLong hitCnt;
+
+    /** number of misses */
+    protected AtomicLong missCnt;
+
+    /** number of puts */
+    protected AtomicLong putCnt;
+
+    /**
+     * For post reflection creation initialization
+     * <p>
+     * @param hub
+     */
+    @Override
+    public void initialize( CompositeCache<K, V> hub )
+    {
+        hitCnt = new AtomicLong(0);
+        missCnt = new AtomicLong(0);
+        putCnt = new AtomicLong(0);
+
+        this.cacheAttributes = hub.getCacheAttributes();
+        this.chunkSize = cacheAttributes.getSpoolChunkSize();
+        this.cache = hub;
+
+        this.map = createMap();
+    }
+
+    /**
+     * Children must implement this method. A FIFO implementation may use a tree map. An LRU might
+     * use a hashtable. The map returned should be threadsafe.
+     * <p>
+     * @return a threadsafe Map
+     */
+    public abstract Map<K, MemoryElementDescriptor<K, V>> createMap();
+
+    /**
+     * Gets multiple items from the cache based on the given set of keys.
+     * <p>
+     * @param keys
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache for any of these keys
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMultiple(Set<K> keys)
+        throws IOException
+    {
+        if (keys != null)
+        {
+            return keys.stream()
+                .map(key -> {
+                    try
+                    {
+                        return get(key);
+                    }
+                    catch (IOException e)
+                    {
+                        return null;
+                    }
+                })
+                .filter(element -> element != null)
+                .collect(Collectors.toMap(
+                        element -> element.getKey(),
+                        element -> element));
+        }
+
+        return new HashMap<>();
+    }
+
+    /**
+     * Get an item from the cache without affecting its last access time or position. Not all memory
+     * cache implementations can get quietly.
+     * <p>
+     * @param key Identifies item to find
+     * @return Element matching key if found, or null
+     * @throws IOException
+     */
+    @Override
+    public ICacheElement<K, V> getQuiet( K key )
+        throws IOException
+    {
+        ICacheElement<K, V> ce = null;
+
+        MemoryElementDescriptor<K, V> me = map.get( key );
+        if ( me != null )
+        {
+            log.debug( "{0}: MemoryCache quiet hit for {1}",
+                    () -> getCacheName(), () -> key );
+
+            ce = me.getCacheElement();
+        }
+        else
+        {
+            log.debug( "{0}: MemoryCache quiet miss for {1}",
+                    () -> getCacheName(), () -> key );
+        }
+
+        return ce;
+    }
+
+    /**
+     * Puts an item to the cache.
+     * <p>
+     * @param ce Description of the Parameter
+     * @throws IOException Description of the Exception
+     */
+    @Override
+    public abstract void update( ICacheElement<K, V> ce )
+        throws IOException;
+
+    /**
+     * Removes all cached items from the cache.
+     * <p>
+     * @throws IOException
+     */
+    @Override
+    public void removeAll() throws IOException
+    {
+        lock.lock();
+        try
+        {
+            lockedRemoveAll();
+            map.clear();
+        }
+        finally
+        {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Removes all cached items from the cache control structures.
+     * (guarded by the lock)
+     */
+    protected abstract void lockedRemoveAll();
+
+    /**
+     * Prepares for shutdown. Reset statistics
+     * <p>
+     * @throws IOException
+     */
+    @Override
+    public void dispose()
+        throws IOException
+    {
+        removeAll();
+        hitCnt.set(0);
+        missCnt.set(0);
+        putCnt.set(0);
+        log.info( "Memory Cache dispose called." );
+    }
+
+    /**
+     * @return statistics about the cache
+     */
+    @Override
+    public IStats getStatistics()
+    {
+        IStats stats = new Stats();
+        stats.setTypeName( "Abstract Memory Cache" );
+
+        ArrayList<IStatElement<?>> elems = new ArrayList<>();
+        stats.setStatElements(elems);
+
+        elems.add(new StatElement<>("Put Count", putCnt));
+        elems.add(new StatElement<>("Hit Count", hitCnt));
+        elems.add(new StatElement<>("Miss Count", missCnt));
+        elems.add(new StatElement<>( "Map Size", Integer.valueOf(getSize()) ) );
+
+        return stats;
+    }
+
+    /**
+     * Returns the current cache size.
+     * <p>
+     * @return The size value
+     */
+    @Override
+    public int getSize()
+    {
+        return this.map.size();
+    }
+
+    /**
+     * Returns the cache (aka "region") name.
+     * <p>
+     * @return The cacheName value
+     */
+    public String getCacheName()
+    {
+        String attributeCacheName = this.cacheAttributes.getCacheName();
+        if(attributeCacheName != null)
+        {
+            return attributeCacheName;
+        }
+        return cache.getCacheName();
+    }
+
+    /**
+     * Puts an item to the cache.
+     * <p>
+     * @param ce the item
+     */
+    @Override
+    public void waterfal( ICacheElement<K, V> ce )
+    {
+        this.cache.spoolToDisk( ce );
+    }
+
+    // ---------------------------------------------------------- debug method
+    /**
+     * Dump the cache map for debugging.
+     */
+    public void dumpMap()
+    {
+        if (log.isTraceEnabled())
+        {
+            log.trace("dumpingMap");
+            map.entrySet().forEach(e ->
+                log.trace("dumpMap> key={0}, val={1}", e.getKey(),
+                        e.getValue().getCacheElement().getVal()));
+        }
+    }
+
+    /**
+     * Returns the CacheAttributes.
+     * <p>
+     * @return The CacheAttributes value
+     */
+    @Override
+    public ICompositeCacheAttributes getCacheAttributes()
+    {
+        return this.cacheAttributes;
+    }
+
+    /**
+     * Sets the CacheAttributes.
+     * <p>
+     * @param cattr The new CacheAttributes value
+     */
+    @Override
+    public void setCacheAttributes( ICompositeCacheAttributes cattr )
+    {
+        this.cacheAttributes = cattr;
+    }
+
+    /**
+     * Gets the cache hub / region that the MemoryCache is used by
+     * <p>
+     * @return The cache value
+     */
+    @Override
+    public CompositeCache<K, V> getCompositeCache()
+    {
+        return this.cache;
+    }
+
+    /**
+     * Remove all keys of the same group hierarchy.
+     * @param key the key
+     * @return true if something has been removed
+     */
+    protected boolean removeByGroup(K key)
+    {
+        GroupId groupId = ((GroupAttrName<?>) key).groupId;
+
+        // remove all keys of the same group hierarchy.
+        return map.entrySet().removeIf(entry -> {
+            K k = entry.getKey();
+
+            if (k instanceof GroupAttrName && ((GroupAttrName<?>) k).groupId.equals(groupId))
+            {
+                lock.lock();
+                try
+                {
+                    lockedRemoveElement(entry.getValue());
+                    return true;
+                }
+                finally
+                {
+                    lock.unlock();
+                }
+            }
+
+            return false;
+        });
+    }
+
+    /**
+     * Remove all keys of the same name hierarchy.
+     *
+     * @param key the key
+     * @return true if something has been removed
+     */
+    protected boolean removeByHierarchy(K key)
+    {
+        String keyString = key.toString();
+
+        // remove all keys of the same name hierarchy.
+        return map.entrySet().removeIf(entry -> {
+            K k = entry.getKey();
+
+            if (k instanceof String && ((String) k).startsWith(keyString))
+            {
+                lock.lock();
+                try
+                {
+                    lockedRemoveElement(entry.getValue());
+                    return true;
+                }
+                finally
+                {
+                    lock.unlock();
+                }
+            }
+
+            return false;
+        });
+    }
+
+    /**
+     * Remove element from control structure
+     * (guarded by the lock)
+     *
+     * @param me the memory element descriptor
+     */
+    protected abstract void lockedRemoveElement(MemoryElementDescriptor<K, V> me);
+
+    /**
+     * Removes an item from the cache. This method handles hierarchical removal. If the key is a
+     * String and ends with the CacheConstants.NAME_COMPONENT_DELIMITER, then all items with keys
+     * starting with the argument String will be removed.
+     * <p>
+     *
+     * @param key
+     * @return true if the removal was successful
+     * @throws IOException
+     */
+    @Override
+    public boolean remove(K key) throws IOException
+    {
+        log.debug("removing item for key: {0}", key);
+
+        boolean removed = false;
+
+        // handle partial removal
+        if (key instanceof String && ((String) key).endsWith(ICache.NAME_COMPONENT_DELIMITER))
+        {
+            removed = removeByHierarchy(key);
+        }
+        else if (key instanceof GroupAttrName && ((GroupAttrName<?>) key).attrName == null)
+        {
+            removed = removeByGroup(key);
+        }
+        else
+        {
+            // remove single item.
+            lock.lock();
+            try
+            {
+                MemoryElementDescriptor<K, V> me = map.remove(key);
+                if (me != null)
+                {
+                    lockedRemoveElement(me);
+                    removed = true;
+                }
+            }
+            finally
+            {
+                lock.unlock();
+            }
+        }
+
+        return removed;
+    }
+
+    /**
+     * Get an Array of the keys for all elements in the memory cache
+     *
+     * @return An Object[]
+     */
+    @Override
+    public Set<K> getKeySet()
+    {
+        return new LinkedHashSet<>(map.keySet());
+    }
+
+    /**
+     * Get an item from the cache.
+     * <p>
+     *
+     * @param key Identifies item to find
+     * @return ICacheElement&lt;K, V&gt; if found, else null
+     * @throws IOException
+     */
+    @Override
+    public ICacheElement<K, V> get(K key) throws IOException
+    {
+        ICacheElement<K, V> ce = null;
+
+        log.debug("{0}: getting item for key {1}", () -> getCacheName(),
+                () -> key);
+
+        MemoryElementDescriptor<K, V> me = map.get(key);
+
+        if (me != null)
+        {
+            hitCnt.incrementAndGet();
+            ce = me.getCacheElement();
+
+            lock.lock();
+            try
+            {
+                lockedGetElement(me);
+            }
+            finally
+            {
+                lock.unlock();
+            }
+
+            log.debug("{0}: MemoryCache hit for {1}", () -> getCacheName(),
+                    () -> key);
+        }
+        else
+        {
+            missCnt.incrementAndGet();
+
+            log.debug("{0}: MemoryCache miss for {1}", () -> getCacheName(),
+                    () -> key);
+        }
+
+        return ce;
+    }
+
+    /**
+     * Update control structures after get
+     * (guarded by the lock)
+     *
+     * @param me the memory element descriptor
+     */
+    protected abstract void lockedGetElement(MemoryElementDescriptor<K, V> me);
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/behavior/IMemoryCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/behavior/IMemoryCache.java
new file mode 100644
index 0000000..fa5b2fe
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/behavior/IMemoryCache.java
@@ -0,0 +1,187 @@
+package org.apache.commons.jcs3.engine.memory.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+import org.apache.commons.jcs3.engine.stats.behavior.IStats;
+
+/** For the framework. Insures methods a MemoryCache needs to access. */
+public interface IMemoryCache<K, V>
+{
+    /**
+     * Initialize the memory cache
+     * <p>
+     * @param cache The cache (region) this memory store is attached to.
+     */
+    void initialize( CompositeCache<K, V> cache );
+
+    /**
+     * Destroy the memory cache
+     * <p>
+     * @throws IOException
+     */
+    void dispose()
+        throws IOException;
+
+    /**
+     * Get the number of elements contained in the memory store
+     * <p>
+     * @return Element count
+     */
+    int getSize();
+
+    /**
+     * Returns the historical and statistical data for a region's memory cache.
+     * <p>
+     * @return Statistics and Info for the Memory Cache.
+     */
+    IStats getStatistics();
+
+    /**
+     * Get a set of the keys for all elements in the memory cache.
+     * <p>
+     * @return a set of the key type
+     * TODO This should probably be done in chunks with a range passed in. This
+     *       will be a problem if someone puts a 1,000,000 or so items in a
+     *       region.
+     */
+    Set<K> getKeySet();
+
+    /**
+     * Removes an item from the cache
+     * <p>
+     * @param key
+     *            Identifies item to be removed
+     * @return Description of the Return Value
+     * @throws IOException
+     *                Description of the Exception
+     */
+    boolean remove( K key )
+        throws IOException;
+
+    /**
+     * Removes all cached items from the cache.
+     * <p>
+     * @throws IOException
+     *                Description of the Exception
+     */
+    void removeAll()
+        throws IOException;
+
+    /**
+     * This instructs the memory cache to remove the <i>numberToFree</i>
+     * according to its eviction policy. For example, the LRUMemoryCache will
+     * remove the <i>numberToFree</i> least recently used items. These will be
+     * spooled to disk if a disk auxiliary is available.
+     * <p>
+     * @param numberToFree
+     * @return the number that were removed. if you ask to free 5, but there are
+     *         only 3, you will get 3.
+     * @throws IOException
+     */
+    int freeElements( int numberToFree )
+        throws IOException;
+
+    /**
+     * Get an item from the cache
+     * <p>
+     * @param key
+     *            Description of the Parameter
+     * @return Description of the Return Value
+     * @throws IOException
+     *                Description of the Exception
+     */
+    ICacheElement<K, V> get( K key )
+        throws IOException;
+
+    /**
+     * Gets multiple items from the cache based on the given set of keys.
+     * <p>
+     * @param keys
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map
+     * if there is no data in cache for any of these keys
+     * @throws IOException
+     */
+    Map<K, ICacheElement<K, V>> getMultiple( Set<K> keys )
+        throws IOException;
+
+    /**
+     * Get an item from the cache without effecting its order or last access
+     * time
+     * <p>
+     * @param key
+     *            Description of the Parameter
+     * @return The quiet value
+     * @throws IOException
+     *                Description of the Exception
+     */
+    ICacheElement<K, V> getQuiet( K key )
+        throws IOException;
+
+    /**
+     * Spools the item contained in the provided element to disk
+     * <p>
+     * @param ce
+     *            Description of the Parameter
+     * @throws IOException
+     *                Description of the Exception
+     */
+    void waterfal( ICacheElement<K, V> ce )
+        throws IOException;
+
+    /**
+     * Puts an item to the cache.
+     * <p>
+     * @param ce
+     *            Description of the Parameter
+     * @throws IOException
+     *                Description of the Exception
+     */
+    void update( ICacheElement<K, V> ce )
+        throws IOException;
+
+    /**
+     * Returns the CacheAttributes for the region.
+     * <p>
+     * @return The cacheAttributes value
+     */
+    ICompositeCacheAttributes getCacheAttributes();
+
+    /**
+     * Sets the CacheAttributes of the region.
+     * <p>
+     * @param cattr
+     *            The new cacheAttributes value
+     */
+    void setCacheAttributes( ICompositeCacheAttributes cattr );
+
+    /**
+     * Gets the cache hub / region that uses the MemoryCache.
+     * <p>
+     * @return The cache value
+     */
+    CompositeCache<K, V> getCompositeCache();
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/fifo/FIFOMemoryCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/fifo/FIFOMemoryCache.java
new file mode 100644
index 0000000..33621c4
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/fifo/FIFOMemoryCache.java
@@ -0,0 +1,59 @@
+package org.apache.commons.jcs3.engine.memory.fifo;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.memory.AbstractDoubleLinkedListMemoryCache;
+import org.apache.commons.jcs3.engine.memory.util.MemoryElementDescriptor;
+
+/**
+ * The items are spooled in the order they are added. No adjustments to the list are made on get.
+ */
+public class FIFOMemoryCache<K, V>
+    extends AbstractDoubleLinkedListMemoryCache<K, V>
+{
+    /**
+     * Puts an item to the cache. Removes any pre-existing entries of the same key from the linked
+     * list and adds this one first.
+     * <p>
+     * @param ce The cache element, or entry wrapper
+     * @return MemoryElementDescriptor the new node
+     * @throws IOException
+     */
+    @Override
+    protected MemoryElementDescriptor<K, V> adjustListForUpdate( ICacheElement<K, V> ce )
+        throws IOException
+    {
+        return addFirst( ce );
+    }
+
+    /**
+     * Does nothing.
+     * <p>
+     * @param me
+     */
+    @Override
+    protected void adjustListForGet( MemoryElementDescriptor<K, V> me )
+    {
+        // DO NOTHING
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/lru/LHMLRUMemoryCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/lru/LHMLRUMemoryCache.java
new file mode 100644
index 0000000..8d9fa7b
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/lru/LHMLRUMemoryCache.java
@@ -0,0 +1,202 @@
+package org.apache.commons.jcs3.engine.memory.lru;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+import org.apache.commons.jcs3.engine.memory.AbstractMemoryCache;
+import org.apache.commons.jcs3.engine.memory.util.MemoryElementDescriptor;
+import org.apache.commons.jcs3.engine.stats.behavior.IStats;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * This is a test memory manager using the jdk1.4 LinkedHashMap.
+ */
+public class LHMLRUMemoryCache<K, V>
+    extends AbstractMemoryCache<K, V>
+{
+    /** The Logger. */
+    private static final Log log = LogManager.getLog( LRUMemoryCache.class );
+
+    /**
+     * For post reflection creation initialization
+     * <p>
+     * @param hub
+     */
+    @Override
+    public void initialize( CompositeCache<K, V> hub )
+    {
+        super.initialize( hub );
+        log.info( "initialized LHMLRUMemoryCache for {0}", () -> getCacheName() );
+    }
+
+    /**
+     * Returns a synchronized LHMSpooler
+     * <p>
+     * @return Collections.synchronizedMap( new LHMSpooler() )
+     */
+    @Override
+    public Map<K, MemoryElementDescriptor<K, V>> createMap()
+    {
+        return Collections.synchronizedMap( new LHMSpooler() );
+    }
+
+    /**
+     * Puts an item to the cache.
+     * <p>
+     * @param ce Description of the Parameter
+     * @throws IOException
+     */
+    @Override
+    public void update( ICacheElement<K, V> ce )
+        throws IOException
+    {
+        putCnt.incrementAndGet();
+        map.put( ce.getKey(), new MemoryElementDescriptor<>(ce) );
+    }
+
+    /**
+     * Update control structures after get
+     * (guarded by the lock)
+     *
+     * @param me the memory element descriptor
+     */
+    @Override
+    protected void lockedGetElement(MemoryElementDescriptor<K, V> me)
+    {
+        // empty
+    }
+
+    /**
+     * Remove element from control structure
+     * (guarded by the lock)
+     *
+     * @param me the memory element descriptor
+     */
+    @Override
+    protected void lockedRemoveElement(MemoryElementDescriptor<K, V> me)
+    {
+        // empty
+    }
+
+    /**
+     * Removes all cached items from the cache control structures.
+     * (guarded by the lock)
+     */
+    @Override
+    protected void lockedRemoveAll()
+    {
+        // empty
+    }
+
+    /**
+     * This returns semi-structured information on the memory cache, such as the size, put count,
+     * hit count, and miss count.
+     * <p>
+     * @return IStats
+     */
+    @Override
+    public IStats getStatistics()
+    {
+        IStats stats = super.getStatistics();
+        stats.setTypeName( "LHMLRU Memory Cache" );
+
+        return stats;
+    }
+
+    // ---------------------------------------------------------- debug methods
+
+    /**
+     * Dump the cache entries from first to last for debugging.
+     */
+    public void dumpCacheEntries()
+    {
+        dumpMap();
+    }
+
+    /**
+     * This can't be implemented.
+     * <p>
+     * @param numberToFree
+     * @return 0
+     * @throws IOException
+     */
+    @Override
+    public int freeElements( int numberToFree )
+        throws IOException
+    {
+        // can't be implemented using the LHM
+        return 0;
+    }
+
+    // ---------------------------------------------------------- extended map
+
+    /**
+     * Implementation of removeEldestEntry in LinkedHashMap
+     */
+    protected class LHMSpooler
+        extends java.util.LinkedHashMap<K, MemoryElementDescriptor<K, V>>
+    {
+        /** Don't change. */
+        private static final long serialVersionUID = -1255907868906762484L;
+
+        /**
+         * Initialize to a small size--for now, 1/2 of max 3rd variable "true" indicates that it
+         * should be access and not time governed. This could be configurable.
+         */
+        public LHMSpooler()
+        {
+            super( (int) ( getCacheAttributes().getMaxObjects() * .5 ), .75F, true );
+        }
+
+        /**
+         * Remove eldest. Automatically called by LinkedHashMap.
+         * <p>
+         * @param eldest
+         * @return true if removed
+         */
+        @SuppressWarnings("synthetic-access")
+        @Override
+        protected boolean removeEldestEntry( Map.Entry<K, MemoryElementDescriptor<K, V>> eldest )
+        {
+            ICacheElement<K, V> element = eldest.getValue().getCacheElement();
+
+            if ( size() <= getCacheAttributes().getMaxObjects() )
+            {
+                return false;
+            }
+            else
+            {
+                log.debug( "LHMLRU max size: {0}. Spooling element, key: {1}",
+                        () -> getCacheAttributes().getMaxObjects(), () -> element.getKey() );
+
+                waterfal( element );
+
+                log.debug( "LHMLRU size: {0}", () -> map.size() );
+            }
+            return true;
+        }
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/lru/LRUMemoryCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/lru/LRUMemoryCache.java
new file mode 100644
index 0000000..ac40202
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/lru/LRUMemoryCache.java
@@ -0,0 +1,67 @@
+package org.apache.commons.jcs3.engine.memory.lru;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.memory.AbstractDoubleLinkedListMemoryCache;
+import org.apache.commons.jcs3.engine.memory.util.MemoryElementDescriptor;
+
+/**
+ * A fast reference management system. The least recently used items move to the end of the list and
+ * get spooled to disk if the cache hub is configured to use a disk cache. Most of the cache
+ * bottlenecks are in IO. There are no io bottlenecks here, it's all about processing power.
+ * <p>
+ * Even though there are only a few adjustments necessary to maintain the double linked list, we
+ * might want to find a more efficient memory manager for large cache regions.
+ * <p>
+ * The LRUMemoryCache is most efficient when the first element is selected. The smaller the region,
+ * the better the chance that this will be the case. &lt; .04 ms per put, p3 866, 1/10 of that per get
+ */
+public class LRUMemoryCache<K, V>
+    extends AbstractDoubleLinkedListMemoryCache<K, V>
+{
+    /**
+     * Puts an item to the cache. Removes any pre-existing entries of the same key from the linked
+     * list and adds this one first.
+     * <p>
+     * @param ce The cache element, or entry wrapper
+     * @return MemoryElementDescriptor the new node
+     * @throws IOException
+     */
+    @Override
+    protected MemoryElementDescriptor<K, V> adjustListForUpdate( ICacheElement<K, V> ce )
+        throws IOException
+    {
+        return addFirst( ce );
+    }
+
+    /**
+     * Makes the item the first in the list.
+     * <p>
+     * @param me
+     */
+    @Override
+    protected void adjustListForGet( MemoryElementDescriptor<K, V> me )
+    {
+        list.makeFirst( me );
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/lru/package.html b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/lru/package.html
similarity index 100%
rename from commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/lru/package.html
rename to commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/lru/package.html
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/mru/MRUMemoryCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/mru/MRUMemoryCache.java
new file mode 100644
index 0000000..2cf7fcb
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/mru/MRUMemoryCache.java
@@ -0,0 +1,62 @@
+package org.apache.commons.jcs3.engine.memory.mru;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.memory.AbstractDoubleLinkedListMemoryCache;
+import org.apache.commons.jcs3.engine.memory.util.MemoryElementDescriptor;
+
+/**
+ * The most recently used items move to the front of the list and get spooled to disk if the cache
+ * hub is configured to use a disk cache.
+ */
+public class MRUMemoryCache<K, V>
+    extends AbstractDoubleLinkedListMemoryCache<K, V>
+{
+    /**
+     * Adds the item to the front of the list. A put doesn't count as a usage.
+     * <p>
+     * It's not clear if the put operation should be different. Perhaps this should remove the oldest
+     * if full, and then put.
+     * <p>
+     * @param ce
+     * @return MemoryElementDescriptor the new node
+     * @throws IOException
+     */
+    @Override
+    protected MemoryElementDescriptor<K, V> adjustListForUpdate( ICacheElement<K, V> ce )
+        throws IOException
+    {
+        return addFirst( ce );
+    }
+
+    /**
+     * Makes the item the last in the list.
+     * <p>
+     * @param me
+     */
+    @Override
+    protected void adjustListForGet( MemoryElementDescriptor<K, V> me )
+    {
+        list.makeLast( me );
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/mru/package.html b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/mru/package.html
similarity index 100%
rename from commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/mru/package.html
rename to commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/mru/package.html
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/package.html b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/package.html
similarity index 100%
rename from commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/package.html
rename to commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/package.html
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/shrinking/ShrinkerThread.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/shrinking/ShrinkerThread.java
new file mode 100644
index 0000000..ee01831
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/shrinking/ShrinkerThread.java
@@ -0,0 +1,202 @@
+package org.apache.commons.jcs3.engine.memory.shrinking;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Set;
+
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+import org.apache.commons.jcs3.engine.control.event.behavior.ElementEventType;
+import org.apache.commons.jcs3.engine.memory.behavior.IMemoryCache;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * A background memory shrinker. Memory problems and concurrent modification exception caused by
+ * acting directly on an iterator of the underlying memory cache should have been solved.
+ * @version $Id$
+ */
+public class ShrinkerThread<K, V>
+    implements Runnable
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog( ShrinkerThread.class );
+
+    /** The CompositeCache instance which this shrinker is watching */
+    private final CompositeCache<K, V> cache;
+
+    /** Maximum memory idle time for the whole cache */
+    private final long maxMemoryIdleTime;
+
+    /** Maximum number of items to spool per run. Default is -1, or no limit. */
+    private final int maxSpoolPerRun;
+
+    /** Should we limit the number spooled per run. If so, the maxSpoolPerRun will be used. */
+    private boolean spoolLimit = false;
+
+    /**
+     * Constructor for the ShrinkerThread object.
+     * <p>
+     * @param cache The MemoryCache which the new shrinker should watch.
+     */
+    public ShrinkerThread( CompositeCache<K, V> cache )
+    {
+        super();
+
+        this.cache = cache;
+
+        long maxMemoryIdleTimeSeconds = cache.getCacheAttributes().getMaxMemoryIdleTimeSeconds();
+
+        if ( maxMemoryIdleTimeSeconds < 0 )
+        {
+            this.maxMemoryIdleTime = -1;
+        }
+        else
+        {
+            this.maxMemoryIdleTime = maxMemoryIdleTimeSeconds * 1000;
+        }
+
+        this.maxSpoolPerRun = cache.getCacheAttributes().getMaxSpoolPerRun();
+        if ( this.maxSpoolPerRun != -1 )
+        {
+            this.spoolLimit = true;
+        }
+
+    }
+
+    /**
+     * Main processing method for the ShrinkerThread object
+     */
+    @Override
+    public void run()
+    {
+        shrink();
+    }
+
+    /**
+     * This method is called when the thread wakes up. First the method obtains an array of keys for
+     * the cache region. It iterates through the keys and tries to get the item from the cache
+     * without affecting the last access or position of the item. The item is checked for
+     * expiration, the expiration check has 3 parts:
+     * <ol>
+     * <li>Has the cacheattributes.MaxMemoryIdleTimeSeconds defined for the region been exceeded? If
+     * so, the item should be move to disk.</li> <li>Has the item exceeded MaxLifeSeconds defined in
+     * the element attributes? If so, remove it.</li> <li>Has the item exceeded IdleTime defined in
+     * the element attributes? If so, remove it. If there are event listeners registered for the
+     * cache element, they will be called.</li>
+     * </ol>
+     * TODO Change element event handling to use the queue, then move the queue to the region and
+     *       access via the Cache.
+     */
+    protected void shrink()
+    {
+        log.debug( "Shrinking memory cache for: {0}", () -> this.cache.getCacheName() );
+
+        IMemoryCache<K, V> memCache = cache.getMemoryCache();
+
+        try
+        {
+            Set<K> keys = memCache.getKeySet();
+            int size = keys.size();
+            log.debug( "Keys size: {0}", size );
+
+            int spoolCount = 0;
+
+            for (K key : keys)
+            {
+                final ICacheElement<K, V> cacheElement = memCache.getQuiet( key );
+
+                if ( cacheElement == null )
+                {
+                    continue;
+                }
+
+                IElementAttributes attributes = cacheElement.getElementAttributes();
+
+                boolean remove = false;
+
+                long now = System.currentTimeMillis();
+
+                // If the element is not eternal, check if it should be
+                // removed and remove it if so.
+                if ( !attributes.getIsEternal() )
+                {
+                    remove = cache.isExpired( cacheElement, now,
+                            ElementEventType.EXCEEDED_MAXLIFE_BACKGROUND,
+                            ElementEventType.EXCEEDED_IDLETIME_BACKGROUND );
+
+                    if ( remove )
+                    {
+                        memCache.remove( key );
+                    }
+                }
+
+                // If the item is not removed, check is it has been idle
+                // long enough to be spooled.
+
+                if ( !remove && maxMemoryIdleTime != -1 )
+                {
+                    if ( !spoolLimit || spoolCount < this.maxSpoolPerRun )
+                    {
+                        final long lastAccessTime = attributes.getLastAccessTime();
+
+                        if ( lastAccessTime + maxMemoryIdleTime < now )
+                        {
+                            log.debug( "Exceeded memory idle time: {0}", key );
+
+                            // Shouldn't we ensure that the element is
+                            // spooled before removing it from memory?
+                            // No the disk caches have a purgatory. If it fails
+                            // to spool that does not affect the
+                            // responsibilities of the memory cache.
+
+                            spoolCount++;
+
+                            memCache.remove( key );
+                            memCache.waterfal( cacheElement );
+                        }
+                    }
+                    else
+                    {
+                        log.debug( "spoolCount = \"{0}\"; maxSpoolPerRun = \"{1}\"",
+                                spoolCount, maxSpoolPerRun );
+
+                        // stop processing if limit has been reached.
+                        if ( spoolLimit && spoolCount >= this.maxSpoolPerRun )
+                        {
+                            return;
+                        }
+                    }
+                }
+            }
+        }
+        catch ( Throwable t )
+        {
+            log.info( "Unexpected trouble in shrink cycle", t );
+
+            // concurrent modifications should no longer be a problem
+            // It is up to the IMemoryCache to return an array of keys
+
+            // stop for now
+            return;
+        }
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/soft/SoftReferenceMemoryCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/soft/SoftReferenceMemoryCache.java
new file mode 100644
index 0000000..9776ac2
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/soft/SoftReferenceMemoryCache.java
@@ -0,0 +1,240 @@
+package org.apache.commons.jcs3.engine.memory.soft;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.lang.ref.SoftReference;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+import org.apache.commons.jcs3.engine.memory.AbstractMemoryCache;
+import org.apache.commons.jcs3.engine.memory.util.MemoryElementDescriptor;
+import org.apache.commons.jcs3.engine.memory.util.SoftReferenceElementDescriptor;
+import org.apache.commons.jcs3.engine.stats.StatElement;
+import org.apache.commons.jcs3.engine.stats.behavior.IStatElement;
+import org.apache.commons.jcs3.engine.stats.behavior.IStats;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * A JCS IMemoryCache that has {@link SoftReference} to all its values.
+ * This cache does not respect {@link ICompositeCacheAttributes#getMaxObjects()}
+ * as overflowing is handled by Java GC.
+ * <p>
+ * The cache also has strong references to a maximum number of objects given by
+ * the maxObjects parameter
+ *
+ * @author halset
+ */
+public class SoftReferenceMemoryCache<K, V> extends AbstractMemoryCache<K, V>
+{
+    /** The logger. */
+    private static final Log log = LogManager.getLog(SoftReferenceMemoryCache.class);
+
+    /**
+     * Strong references to the maxObjects number of newest objects.
+     * <p>
+     * Trimming is done by {@link #trimStrongReferences()} instead of by
+     * overriding removeEldestEntry to be able to control waterfalling as easy
+     * as possible
+     */
+    private LinkedBlockingQueue<ICacheElement<K, V>> strongReferences;
+
+    /**
+     * For post reflection creation initialization
+     * <p>
+     * @param hub
+     */
+    @Override
+    public synchronized void initialize( CompositeCache<K, V> hub )
+    {
+        super.initialize( hub );
+        strongReferences = new LinkedBlockingQueue<>();
+        log.info( "initialized Soft Reference Memory Cache for {0}",
+                () -> getCacheName() );
+    }
+
+    /**
+     * @see org.apache.commons.jcs3.engine.memory.AbstractMemoryCache#createMap()
+     */
+    @Override
+    public ConcurrentMap<K, MemoryElementDescriptor<K, V>> createMap()
+    {
+        return new ConcurrentHashMap<>();
+    }
+
+    /**
+     * @see org.apache.commons.jcs3.engine.memory.behavior.IMemoryCache#getKeySet()
+     */
+    @Override
+    public Set<K> getKeySet()
+    {
+        Set<K> keys = new HashSet<>();
+        for (Map.Entry<K, MemoryElementDescriptor<K, V>> e : map.entrySet())
+        {
+            SoftReferenceElementDescriptor<K, V> sred = (SoftReferenceElementDescriptor<K, V>) e.getValue();
+            if (sred.getCacheElement() != null)
+            {
+                keys.add(e.getKey());
+            }
+        }
+
+        return keys;
+    }
+
+    /**
+     * Returns the current cache size.
+     * <p>
+     * @return The size value
+     */
+    @Override
+    public int getSize()
+    {
+        int size = 0;
+        for (MemoryElementDescriptor<K, V> me : map.values())
+        {
+            SoftReferenceElementDescriptor<K, V> sred = (SoftReferenceElementDescriptor<K, V>) me;
+            if (sred.getCacheElement() != null)
+            {
+                size++;
+            }
+        }
+        return size;
+    }
+
+    /**
+     * @return statistics about the cache
+     */
+    @Override
+    public IStats getStatistics()
+    {
+        IStats stats = super.getStatistics();
+        stats.setTypeName("Soft Reference Memory Cache");
+
+        List<IStatElement<?>> elems = stats.getStatElements();
+        int emptyrefs = map.size() - getSize();
+        elems.add(new StatElement<>("Empty References", Integer.valueOf(emptyrefs)));
+        elems.add(new StatElement<>("Strong References", Integer.valueOf(strongReferences.size())));
+
+        return stats;
+    }
+
+    /**
+     * Update control structures after get
+     * (guarded by the lock)
+     *
+     * @param me the memory element descriptor
+     */
+    @Override
+    protected void lockedGetElement(MemoryElementDescriptor<K, V> me)
+    {
+        ICacheElement<K, V> val = me.getCacheElement();
+        val.getElementAttributes().setLastAccessTimeNow();
+
+        // update the ordering of the strong references
+        strongReferences.add(val);
+        trimStrongReferences();
+    }
+
+    /**
+     * Remove element from control structure
+     * (guarded by the lock)
+     *
+     * @param me the memory element descriptor
+     */
+    @Override
+    protected void lockedRemoveElement(MemoryElementDescriptor<K, V> me)
+    {
+        strongReferences.remove(me.getCacheElement());
+    }
+
+    /**
+     * Removes all cached items from the cache control structures.
+     * (guarded by the lock)
+     */
+    @Override
+    protected void lockedRemoveAll()
+    {
+        strongReferences.clear();
+    }
+
+    /**
+     * Puts an item to the cache.
+     * <p>
+     * @param ce Description of the Parameter
+     * @throws IOException Description of the Exception
+     */
+    @Override
+    public void update(ICacheElement<K, V> ce) throws IOException
+    {
+        putCnt.incrementAndGet();
+        ce.getElementAttributes().setLastAccessTimeNow();
+
+        lock.lock();
+
+        try
+        {
+            map.put(ce.getKey(), new SoftReferenceElementDescriptor<>(ce));
+            strongReferences.add(ce);
+            trimStrongReferences();
+        }
+        finally
+        {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Trim the number of strong references to equal or below the number given
+     * by the maxObjects parameter.
+     */
+    private void trimStrongReferences()
+    {
+        int max = getCacheAttributes().getMaxObjects();
+        int startsize = strongReferences.size();
+
+        for (int cursize = startsize; cursize > max; cursize--)
+        {
+            ICacheElement<K, V> ce = strongReferences.poll();
+            waterfal(ce);
+        }
+    }
+
+    /**
+     * This can't be implemented.
+     * <p>
+     * @param numberToFree
+     * @return 0
+     * @throws IOException
+     */
+    @Override
+    public int freeElements(int numberToFree) throws IOException
+    {
+        return 0;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/soft/package.html b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/soft/package.html
similarity index 100%
rename from commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/memory/soft/package.html
rename to commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/soft/package.html
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/util/MemoryElementDescriptor.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/util/MemoryElementDescriptor.java
new file mode 100644
index 0000000..7577016
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/util/MemoryElementDescriptor.java
@@ -0,0 +1,53 @@
+package org.apache.commons.jcs3.engine.memory.util;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.utils.struct.DoubleLinkedListNode;
+
+/**
+ * This wrapper is needed for double linked lists.
+ */
+public class MemoryElementDescriptor<K, V>
+    extends DoubleLinkedListNode<ICacheElement<K, V>>
+{
+    /** Don't change */
+    private static final long serialVersionUID = -1905161209035522460L;
+
+    /**
+     * Constructs a usable MemoryElementDescriptor.
+     * <p>
+     * @param ce
+     */
+    public MemoryElementDescriptor( ICacheElement<K, V> ce )
+    {
+        super( ce );
+    }
+
+    /**
+     * Get the cache element
+     *
+     * @return the ce
+     */
+    public ICacheElement<K, V> getCacheElement()
+    {
+        return getPayload();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/util/SoftReferenceElementDescriptor.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/util/SoftReferenceElementDescriptor.java
new file mode 100644
index 0000000..4b001ec
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/util/SoftReferenceElementDescriptor.java
@@ -0,0 +1,62 @@
+package org.apache.commons.jcs3.engine.memory.util;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.lang.ref.SoftReference;
+
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+
+/**
+ * This wrapper is needed for double linked lists.
+ */
+public class SoftReferenceElementDescriptor<K, V>
+    extends MemoryElementDescriptor<K, V>
+{
+    /** Don't change */
+    private static final long serialVersionUID = -1905161209035522460L;
+
+    /** The CacheElement wrapped by this descriptor */
+    private final SoftReference<ICacheElement<K, V>> srce;
+
+    /**
+     * Constructs a usable MemoryElementDescriptor.
+     * <p>
+     * @param ce
+     */
+    public SoftReferenceElementDescriptor( ICacheElement<K, V> ce )
+    {
+        super( null );
+        this.srce = new SoftReference<>(ce);
+    }
+
+    /**
+     * @return the ce
+     */
+    @Override
+    public ICacheElement<K, V> getCacheElement()
+    {
+        if (srce != null)
+        {
+            return srce.get();
+        }
+
+        return null;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/package.html b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/package.html
similarity index 100%
rename from commons-jcs-core/src/main/java/org/apache/commons/jcs/engine/package.html
rename to commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/package.html
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/stats/CacheStats.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/stats/CacheStats.java
new file mode 100644
index 0000000..6f6c9d5
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/stats/CacheStats.java
@@ -0,0 +1,116 @@
+package org.apache.commons.jcs3.engine.stats;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.List;
+
+import org.apache.commons.jcs3.engine.stats.behavior.ICacheStats;
+import org.apache.commons.jcs3.engine.stats.behavior.IStats;
+
+/**
+ * This class stores cache historical and statistics data for a region.
+ * <p>
+ * Only the composite cache knows what the hit count across all auxiliaries is.
+ */
+public class CacheStats
+    extends Stats
+    implements ICacheStats
+{
+    /** Don't change. */
+    private static final long serialVersionUID = 529914708798168590L;
+
+    /** The region */
+    private String regionName = null;
+
+    /** What that auxiliaries are reporting. */
+    private List<IStats> auxStats = null;
+
+    /**
+     * Stats are for a region, though auxiliary data may be for more.
+     * <p>
+     * @return The region name
+     */
+    @Override
+    public String getRegionName()
+    {
+        return regionName;
+    }
+
+    /**
+     * Stats are for a region, though auxiliary data may be for more.
+     * <p>
+     * @param name - The region name
+     */
+    @Override
+    public void setRegionName( String name )
+    {
+        regionName = name;
+    }
+
+    /**
+     * @return IStats[]
+     */
+    @Override
+    public List<IStats> getAuxiliaryCacheStats()
+    {
+        return auxStats;
+    }
+
+    /**
+     * @param stats
+     */
+    @Override
+    public void setAuxiliaryCacheStats( List<IStats> stats )
+    {
+        auxStats = stats;
+    }
+
+    /**
+     * @return readable string that can be logged.
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+
+        buf.append( "Region Name = " + regionName );
+
+        if ( getStatElements() != null )
+        {
+            for ( Object stat : getStatElements() )
+            {
+                buf.append( "\n" );
+                buf.append( stat );
+            }
+        }
+
+        if ( auxStats != null )
+        {
+            for ( Object auxStat : auxStats )
+            {
+                buf.append( "\n" );
+                buf.append( "---------------------------" );
+                buf.append( auxStat );
+            }
+        }
+
+        return buf.toString();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/stats/StatElement.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/stats/StatElement.java
new file mode 100644
index 0000000..53b997c
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/stats/StatElement.java
@@ -0,0 +1,104 @@
+package org.apache.commons.jcs3.engine.stats;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.engine.stats.behavior.IStatElement;
+
+/**
+ * This is a stat data holder.
+ */
+public class StatElement<V>
+    implements IStatElement<V>
+{
+    /** Don't change */
+    private static final long serialVersionUID = -2982373725267618092L;
+
+    /** name of the stat */
+    private String name = null;
+
+    /** the data */
+    private V data = null;
+
+    /**
+     * Constructor
+     *
+     * @param name
+     * @param data
+     */
+    public StatElement(String name, V data)
+    {
+        super();
+        this.name = name;
+        this.data = data;
+    }
+
+    /**
+     * Get the name of the stat element, ex. HitCount
+     * <p>
+     * @return the stat element name
+     */
+    @Override
+    public String getName()
+    {
+        return name;
+    }
+
+    /**
+     * @param name
+     */
+    @Override
+    public void setName( String name )
+    {
+        this.name = name;
+    }
+
+    /**
+     * Get the data, ex. for hit count you would get a value for some number.
+     * <p>
+     * @return data
+     */
+    @Override
+    public V getData()
+    {
+        return data;
+    }
+
+    /**
+     * Set the data for this element.
+     * <p>
+     * @param data
+     */
+    @Override
+    public void setData( V data )
+    {
+        this.data = data;
+    }
+
+    /**
+     * @return a readable string.
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        buf.append( name ).append(" = ").append( data );
+        return buf.toString();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/stats/Stats.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/stats/Stats.java
new file mode 100644
index 0000000..db420a8
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/stats/Stats.java
@@ -0,0 +1,99 @@
+package org.apache.commons.jcs3.engine.stats;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.List;
+
+import org.apache.commons.jcs3.engine.stats.behavior.IStatElement;
+import org.apache.commons.jcs3.engine.stats.behavior.IStats;
+
+/**
+ * @author aaronsm
+ */
+public class Stats
+    implements IStats
+{
+    /** Don't change */
+    private static final long serialVersionUID = 227327902875154010L;
+
+    /** The stats */
+    private List<IStatElement<?>> stats = null;
+
+    /** The type of stat */
+    private String typeName = null;
+
+    /**
+     * @return IStatElement[]
+     */
+    @Override
+    public List<IStatElement<?>> getStatElements()
+    {
+        return stats;
+    }
+
+    /**
+     * @param stats
+     */
+    @Override
+    public void setStatElements( List<IStatElement<?>> stats )
+    {
+        this.stats = stats;
+    }
+
+    /**
+     * @return typeName
+     */
+    @Override
+    public String getTypeName()
+    {
+        return typeName;
+    }
+
+    /**
+     * @param name
+     */
+    @Override
+    public void setTypeName( String name )
+    {
+        typeName = name;
+    }
+
+    /**
+     * @return the stats in a readable string
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+
+        buf.append( typeName );
+
+        if ( stats != null )
+        {
+            for (Object stat : stats)
+            {
+                buf.append( "\n" );
+                buf.append( stat );
+            }
+        }
+
+        return buf.toString();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/stats/behavior/ICacheStats.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/stats/behavior/ICacheStats.java
new file mode 100644
index 0000000..8914e5c
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/stats/behavior/ICacheStats.java
@@ -0,0 +1,51 @@
+package org.apache.commons.jcs3.engine.stats.behavior;
+
+import java.util.List;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * This holds stat information on a region. It contains both auxiliary and core stats.
+ */
+public interface ICacheStats
+    extends IStats
+{
+    /**
+     * Stats are for a region, though auxiliary data may be for more.
+     * <p>
+     * @return The region name
+     */
+    String getRegionName();
+
+    /**
+     * @param name
+     */
+    void setRegionName( String name );
+
+    /**
+     * @return IStats[]
+     */
+    List<IStats> getAuxiliaryCacheStats();
+
+    /**
+     * @param stats
+     */
+    void setAuxiliaryCacheStats( List<IStats> stats );
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/stats/behavior/IStatElement.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/stats/behavior/IStatElement.java
new file mode 100644
index 0000000..a542477
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/stats/behavior/IStatElement.java
@@ -0,0 +1,54 @@
+package org.apache.commons.jcs3.engine.stats.behavior;
+
+import java.io.Serializable;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * IAuxiliaryCacheStats will hold these IStatElements.
+ */
+public interface IStatElement<V> extends Serializable
+{
+    /**
+     * Get the name of the stat element, ex. HitCount
+     * <p>
+     * @return the stat element name
+     */
+    String getName();
+
+    /**
+     * @param name
+     */
+    void setName( String name );
+
+    /**
+     * Get the data, ex. for hit count you would get a value for some number.
+     * <p>
+     * @return data
+     */
+    V getData();
+
+    /**
+     * Set the data for this element.
+     * <p>
+     * @param data
+     */
+    void setData( V data );
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/stats/behavior/IStats.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/stats/behavior/IStats.java
new file mode 100644
index 0000000..ce6358e
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/stats/behavior/IStats.java
@@ -0,0 +1,63 @@
+package org.apache.commons.jcs3.engine.stats.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * This interface defines the common behavior for a stats holder.
+ *
+ * @author aaronsm
+ *
+ */
+public interface IStats
+    extends Serializable
+{
+
+    /**
+     * Return generic statistical or historical data.
+     *
+     * @return list of IStatElements
+     */
+    List<IStatElement<?>> getStatElements();
+
+    /**
+     * Set the generic statistical or historical data.
+     *
+     * @param stats
+     */
+    void setStatElements( List<IStatElement<?>> stats );
+
+    /**
+     * Get the type name, such as "LRU Memory Cache." No formal type is defined.
+     *
+     * @return String
+     */
+    String getTypeName();
+
+    /**
+     * Set the type name, such as "LRU Memory Cache." No formal type is defined.
+     * If we need formal types, we can use the cachetype param
+     *
+     * @param name
+     */
+    void setTypeName( String name );
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/io/ObjectInputStreamClassLoaderAware.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/io/ObjectInputStreamClassLoaderAware.java
new file mode 100644
index 0000000..757024b
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/io/ObjectInputStreamClassLoaderAware.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectStreamClass;
+import java.lang.reflect.Proxy;
+
+public class ObjectInputStreamClassLoaderAware extends ObjectInputStream {
+    private final ClassLoader classLoader;
+
+    public ObjectInputStreamClassLoaderAware(final InputStream in, final ClassLoader classLoader) throws IOException {
+        super(in);
+        this.classLoader = classLoader != null ? classLoader : Thread.currentThread().getContextClassLoader();
+    }
+
+    @Override
+    protected Class<?> resolveClass(final ObjectStreamClass desc) throws ClassNotFoundException {
+        return Class.forName(BlacklistClassResolver.DEFAULT.check(desc.getName()), false, classLoader);
+    }
+
+    @Override
+    protected Class<?> resolveProxyClass(final String[] interfaces) throws IOException, ClassNotFoundException {
+        final Class<?>[] cinterfaces = new Class[interfaces.length];
+        for (int i = 0; i < interfaces.length; i++) {
+            cinterfaces[i] = Class.forName(interfaces[i], false, classLoader);
+        }
+
+        try {
+            return Proxy.getProxyClass(classLoader, cinterfaces);
+        } catch (IllegalArgumentException e) {
+            throw new ClassNotFoundException(null, e);
+        }
+    }
+
+    private static class BlacklistClassResolver {
+        private static final BlacklistClassResolver DEFAULT = new BlacklistClassResolver(
+            toArray(System.getProperty(
+                "jcs.serialization.class.blacklist",
+                "org.codehaus.groovy.runtime.,org.apache.commons.collections.functors.,org.apache.xalan")),
+            toArray(System.getProperty("jcs.serialization.class.whitelist")));
+
+        private final String[] blacklist;
+        private final String[] whitelist;
+
+        protected BlacklistClassResolver(final String[] blacklist, final String[] whitelist) {
+            this.whitelist = whitelist;
+            this.blacklist = blacklist;
+        }
+
+        protected boolean isBlacklisted(final String name) {
+            return (whitelist != null && !contains(whitelist, name)) || contains(blacklist, name);
+        }
+
+        public final String check(final String name) {
+            if (isBlacklisted(name)) {
+                throw new SecurityException(name + " is not whitelisted as deserialisable, prevented before loading.");
+            }
+            return name;
+        }
+
+        private static String[] toArray(final String property) {
+            return property == null ? null : property.split(" *, *");
+        }
+
+        private static boolean contains(final String[] list, String name) {
+            if (list != null) {
+                for (final String white : list) {
+                    if (name.startsWith(white)) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/log/JulLogAdapter.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/log/JulLogAdapter.java
new file mode 100644
index 0000000..bf52400
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/log/JulLogAdapter.java
@@ -0,0 +1,574 @@
+package org.apache.commons.jcs3.log;
+
+import java.util.function.Supplier;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+/**
+ * This is a wrapper around the <code>java.util.logging.Logger</code> implementing our own
+ * <code>Log</code> interface.
+ * <p>
+ * This is the mapping of the log levels
+ * </p>
+ * <pre>
+ * Java Level   Log Level
+ * SEVERE       FATAL
+ * SEVERE       ERROR
+ * WARNING      WARN
+ * INFO         INFO
+ * FINE         DEBUG
+ * FINER        TRACE
+ * </pre>
+ */
+public class JulLogAdapter implements Log
+{
+    private final Logger logger;
+
+    /**
+     * Construct a JUL Logger wrapper
+     *
+     * @param logger the JUL Logger
+     */
+    public JulLogAdapter(Logger logger)
+    {
+        super();
+        this.logger = logger;
+    }
+
+    private void log(Level level, String message)
+    {
+        if (logger.isLoggable(level))
+        {
+            logger.logp(level, logger.getName(), "", message);
+        }
+    }
+
+    private void log(Level level, Object message)
+    {
+        if (logger.isLoggable(level))
+        {
+            if (message instanceof Throwable)
+            {
+                logger.logp(level, logger.getName(), "", "Exception:", (Throwable) message);
+            }
+            else
+            {
+                logger.logp(level, logger.getName(), "",
+                        message == null ? null : message.toString());
+            }
+        }
+    }
+
+    private void log(Level level, String message, Throwable t)
+    {
+        if (logger.isLoggable(level))
+        {
+            logger.logp(level, logger.getName(), "", message, t);
+        }
+    }
+
+    private void log(Level level, String message, Object... params)
+    {
+        if (logger.isLoggable(level))
+        {
+            MessageFormatter formatter = new MessageFormatter(message, params);
+            if (formatter.hasThrowable())
+            {
+                logger.logp(level, logger.getName(), "",
+                        formatter.getFormattedMessage(), formatter.getThrowable());
+            }
+            else
+            {
+                logger.logp(level, logger.getName(), "",
+                        formatter.getFormattedMessage());
+            }
+        }
+    }
+
+    private void log(Level level, String message, Supplier<?>... paramSuppliers)
+    {
+        if (logger.isLoggable(level))
+        {
+            MessageFormatter formatter = new MessageFormatter(message, paramSuppliers);
+            if (formatter.hasThrowable())
+            {
+                logger.logp(level, logger.getName(), "",
+                        formatter.getFormattedMessage(), formatter.getThrowable());
+            }
+            else
+            {
+                logger.logp(level, logger.getName(), "",
+                        formatter.getFormattedMessage());
+            }
+        }
+    }
+
+    /**
+     * Logs a message object with the DEBUG level.
+     *
+     * @param message the message string to log.
+     */
+    @Override
+    public void debug(String message)
+    {
+        log(Level.FINE, message);
+    }
+
+    /**
+     * Logs a message object with the DEBUG level.
+     *
+     * @param message the message object to log.
+     */
+    @Override
+    public void debug(Object message)
+    {
+        log(Level.FINE, message);
+    }
+
+    /**
+     * Logs a message with parameters at the DEBUG level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param params parameters to the message.
+     * @see #getMessageFactory()
+     */
+    @Override
+    public void debug(String message, Object... params)
+    {
+        log(Level.FINE, message, params);
+    }
+
+    /**
+     * Logs a message with parameters which are only to be constructed if the
+     * logging level is the DEBUG level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param paramSuppliers An array of functions, which when called,
+     *        produce the desired log message parameters.
+     */
+    @Override
+    public void debug(String message, Supplier<?>... paramSuppliers)
+    {
+        log(Level.FINE, message, paramSuppliers);
+    }
+
+    /**
+     * Logs a message at the DEBUG level including the stack trace of the {@link Throwable}
+     * <code>t</code> passed as parameter.
+     *
+     * @param message the message to log.
+     * @param t the exception to log, including its stack trace.
+     */
+    @Override
+    public void debug(String message, Throwable t)
+    {
+        log(Level.FINE, message, t);
+    }
+
+    /**
+     * Logs a message object with the ERROR level.
+     *
+     * @param message the message string to log.
+     */
+    @Override
+    public void error(String message)
+    {
+        log(Level.SEVERE, message);
+    }
+
+    /**
+     * Logs a message object with the ERROR level.
+     *
+     * @param message the message object to log.
+     */
+    @Override
+    public void error(Object message)
+    {
+        log(Level.SEVERE, message);
+    }
+
+    /**
+     * Logs a message with parameters at the ERROR level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param params parameters to the message.
+     */
+    @Override
+    public void error(String message, Object... params)
+    {
+        log(Level.SEVERE, message, params);
+    }
+
+    /**
+     * Logs a message with parameters which are only to be constructed if the
+     * logging level is the ERROR level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param paramSuppliers An array of functions, which when called, produce
+     *        the desired log message parameters.
+     * @since 2.4
+     */
+    @Override
+    public void error(String message, Supplier<?>... paramSuppliers)
+    {
+        log(Level.SEVERE, message, paramSuppliers);
+    }
+
+    /**
+     * Logs a message at the ERROR level including the stack trace of the {@link Throwable}
+     * <code>t</code> passed as parameter.
+     *
+     * @param message the message object to log.
+     * @param t the exception to log, including its stack trace.
+     */
+    @Override
+    public void error(String message, Throwable t)
+    {
+        log(Level.SEVERE, message, t);
+    }
+
+    /**
+     * Logs a message object with the FATAL level.
+     *
+     * @param message the message string to log.
+     */
+    @Override
+    public void fatal(String message)
+    {
+        log(Level.SEVERE, message);
+    }
+
+    /**
+     * Logs a message object with the FATAL level.
+     *
+     * @param message the message object to log.
+     */
+    @Override
+    public void fatal(Object message)
+    {
+        log(Level.SEVERE, message);
+    }
+
+    /**
+     * Logs a message with parameters at the FATAL level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param params parameters to the message.
+     */
+    @Override
+    public void fatal(String message, Object... params)
+    {
+        log(Level.SEVERE, message, params);
+    }
+
+    /**
+     * Logs a message with parameters which are only to be constructed if the
+     * logging level is the FATAL level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param paramSuppliers An array of functions, which when called, produce the
+     *        desired log message parameters.
+     */
+    @Override
+    public void fatal(String message, Supplier<?>... paramSuppliers)
+    {
+        log(Level.SEVERE, message, paramSuppliers);
+    }
+
+    /**
+     * Logs a message at the FATAL level including the stack trace of the {@link Throwable}
+     * <code>t</code> passed as parameter.
+     *
+     * @param message the message object to log.
+     * @param t the exception to log, including its stack trace.
+     */
+    @Override
+    public void fatal(String message, Throwable t)
+    {
+        log(Level.SEVERE, message, t);
+    }
+
+    /**
+     * Gets the logger name.
+     *
+     * @return the logger name.
+     */
+    @Override
+    public String getName()
+    {
+        return logger.getName();
+    }
+
+    /**
+     * Logs a message object with the INFO level.
+     *
+     * @param message the message string to log.
+     */
+    @Override
+    public void info(String message)
+    {
+        log(Level.INFO, message);
+    }
+
+    /**
+     * Logs a message object with the INFO level.
+     *
+     * @param message the message object to log.
+     */
+    @Override
+    public void info(Object message)
+    {
+        log(Level.INFO, message);
+    }
+
+    /**
+     * Logs a message with parameters at the INFO level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param params parameters to the message.
+     */
+    @Override
+    public void info(String message, Object... params)
+    {
+        log(Level.INFO, message, params);
+    }
+
+    /**
+     * Logs a message with parameters which are only to be constructed if the
+     * logging level is the INFO level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param paramSuppliers An array of functions, which when called, produce
+     *        the desired log message parameters.
+     */
+    @Override
+    public void info(String message, Supplier<?>... paramSuppliers)
+    {
+        log(Level.INFO, message, paramSuppliers);
+    }
+
+    /**
+     * Logs a message at the INFO level including the stack trace of the {@link Throwable}
+     * <code>t</code> passed as parameter.
+     *
+     * @param message the message object to log.
+     * @param t the exception to log, including its stack trace.
+     */
+    @Override
+    public void info(String message, Throwable t)
+    {
+        log(Level.INFO, message, t);
+    }
+
+    /**
+     * Checks whether this Logger is enabled for the DEBUG Level.
+     *
+     * @return boolean - {@code true} if this Logger is enabled for level DEBUG, {@code false}
+     *         otherwise.
+     */
+    @Override
+    public boolean isDebugEnabled()
+    {
+        return logger.isLoggable(Level.FINE);
+    }
+
+    /**
+     * Checks whether this Logger is enabled for the ERROR Level.
+     *
+     * @return boolean - {@code true} if this Logger is enabled for level ERROR, {@code false}
+     *         otherwise.
+     */
+    @Override
+    public boolean isErrorEnabled()
+    {
+        return logger.isLoggable(Level.SEVERE);
+    }
+
+    /**
+     * Checks whether this Logger is enabled for the FATAL Level.
+     *
+     * @return boolean - {@code true} if this Logger is enabled for level FATAL, {@code false}
+     *         otherwise.
+     */
+    @Override
+    public boolean isFatalEnabled()
+    {
+        return logger.isLoggable(Level.SEVERE);
+    }
+
+    /**
+     * Checks whether this Logger is enabled for the INFO Level.
+     *
+     * @return boolean - {@code true} if this Logger is enabled for level INFO, {@code false}
+     *         otherwise.
+     */
+    @Override
+    public boolean isInfoEnabled()
+    {
+        return logger.isLoggable(Level.INFO);
+    }
+
+    /**
+     * Checks whether this Logger is enabled for the TRACE level.
+     *
+     * @return boolean - {@code true} if this Logger is enabled for level TRACE, {@code false}
+     *         otherwise.
+     */
+    @Override
+    public boolean isTraceEnabled()
+    {
+        return logger.isLoggable(Level.FINER);
+    }
+
+    /**
+     * Checks whether this Logger is enabled for the WARN Level.
+     *
+     * @return boolean - {@code true} if this Logger is enabled for level WARN, {@code false}
+     *         otherwise.
+     */
+    @Override
+    public boolean isWarnEnabled()
+    {
+        return logger.isLoggable(Level.WARNING);
+    }
+
+    /**
+     * Logs a message object with the TRACE level.
+     *
+     * @param message the message string to log.
+     */
+    @Override
+    public void trace(String message)
+    {
+        log(Level.FINER, message);
+    }
+
+    /**
+     * Logs a message object with the TRACE level.
+     *
+     * @param message the message object to log.
+     */
+    @Override
+    public void trace(Object message)
+    {
+        log(Level.FINER, message);
+    }
+
+    /**
+     * Logs a message with parameters at the TRACE level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param params parameters to the message.
+     */
+    @Override
+    public void trace(String message, Object... params)
+    {
+        log(Level.FINER, message, params);
+    }
+
+    /**
+     * Logs a message with parameters which are only to be constructed if the
+     * logging level is the TRACE level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param paramSuppliers An array of functions, which when called, produce
+     *        the desired log message parameters.
+     */
+    @Override
+    public void trace(String message, Supplier<?>... paramSuppliers)
+    {
+        log(Level.FINER, message, paramSuppliers);
+    }
+
+    /**
+     * Logs a message at the TRACE level including the stack trace of the {@link Throwable}
+     * <code>t</code> passed as parameter.
+     *
+     * @param message the message object to log.
+     * @param t the exception to log, including its stack trace.
+     * @see #debug(String)
+     */
+    @Override
+    public void trace(String message, Throwable t)
+    {
+        log(Level.FINER, message, t);
+    }
+
+    /**
+     * Logs a message object with the WARN level.
+     *
+     * @param message the message string to log.
+     */
+    @Override
+    public void warn(String message)
+    {
+        log(Level.WARNING, message);
+    }
+
+    /**
+     * Logs a message object with the WARN level.
+     *
+     * @param message the message object to log.
+     */
+    @Override
+    public void warn(Object message)
+    {
+        log(Level.WARNING, message);
+    }
+
+    /**
+     * Logs a message with parameters at the WARN level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param params parameters to the message.
+     */
+    @Override
+    public void warn(String message, Object... params)
+    {
+        log(Level.WARNING, message, params);
+    }
+
+    /**
+     * Logs a message with parameters which are only to be constructed if the
+     * logging level is the WARN level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param paramSuppliers An array of functions, which when called, produce
+     *        the desired log message parameters.
+     */
+    @Override
+    public void warn(String message, Supplier<?>... paramSuppliers)
+    {
+        log(Level.WARNING, message, paramSuppliers);
+    }
+
+    /**
+     * Logs a message at the WARN level including the stack trace of the {@link Throwable}
+     * <code>t</code> passed as parameter.
+     *
+     * @param message the message object to log.
+     * @param t the exception to log, including its stack trace.
+     */
+    @Override
+    public void warn(String message, Throwable t)
+    {
+        log(Level.WARNING, message, t);
+    }
+}
\ No newline at end of file
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/log/JulLogFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/log/JulLogFactory.java
new file mode 100644
index 0000000..2edfb1c
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/log/JulLogFactory.java
@@ -0,0 +1,76 @@
+package org.apache.commons.jcs3.log;
+
+import java.util.logging.Logger;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+/**
+ * This is a SPI factory implementation for java.util.logging
+ */
+public class JulLogFactory implements LogFactory
+{
+    /**
+     * Return the name of the Log subsystem managed by this factory
+     *
+     * @return the name of the log subsystem
+     */
+    @Override
+    public String getName()
+    {
+        return "jul";
+    }
+
+    /**
+     * Shutdown the logging system if the logging system supports it.
+     */
+    @Override
+    public void shutdown()
+    {
+        // do nothing
+    }
+
+    /**
+     * Returns a Log using the fully qualified name of the Class as the Log
+     * name.
+     *
+     * @param clazz
+     *            The Class whose name should be used as the Log name.
+     *
+     * @return The Log.
+     */
+    @Override
+    public Log getLog(final Class<?> clazz)
+    {
+        Logger logger = Logger.getLogger(clazz.getName());
+        return new JulLogAdapter(logger);
+    }
+
+    /**
+     * Returns a Log with the specified name.
+     *
+     * @param name
+     *            The logger name.
+     * @return The Log.
+     */
+    @Override
+    public Log getLog(final String name)
+    {
+        Logger logger = Logger.getLogger(name);
+        return new JulLogAdapter(logger);
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/log/Log.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/log/Log.java
new file mode 100644
index 0000000..649a0e3
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/log/Log.java
@@ -0,0 +1,343 @@
+package org.apache.commons.jcs3.log;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+import java.util.function.Supplier;
+
+/**
+ * This is a borrowed and stripped-down version of the log4j2 Logger interface.
+ * All logging operations, except configuration, are done through this interface.
+ *
+ * <p>
+ * The canonical way to obtain a Logger for a class is through {@link LogManager#getLog()}.
+ * Typically, each class should get its own Log named after its fully qualified class name
+ * </p>
+ *
+ * <pre>
+ * public class MyClass {
+ *     private static final Log log = LogManager.getLog(MyClass.class);
+ *     // ...
+ * }
+ * </pre>
+ */
+public interface Log
+{
+    /**
+     * Logs a message object with the DEBUG level.
+     *
+     * @param message the message string to log.
+     */
+    void debug(String message);
+
+    /**
+     * Logs a message object with the DEBUG level.
+     *
+     * @param message the message object to log.
+     */
+    void debug(Object message);
+
+    /**
+     * Logs a message with parameters at the DEBUG level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param params parameters to the message.
+     */
+    void debug(String message, Object... params);
+
+    /**
+     * Logs a message with parameters which are only to be constructed if the
+     * logging level is the DEBUG level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param paramSuppliers An array of functions, which when called, produce
+     *        the desired log message parameters.
+     */
+    void debug(String message, Supplier<?>... paramSuppliers);
+
+    /**
+     * Logs a message at the DEBUG level including the stack trace of the {@link Throwable}
+     * <code>t</code> passed as parameter.
+     *
+     * @param message the message to log.
+     * @param t the exception to log, including its stack trace.
+     */
+    void debug(String message, Throwable t);
+
+    /**
+     * Logs a message object with the ERROR level.
+     *
+     * @param message the message string to log.
+     */
+    void error(String message);
+
+    /**
+     * Logs a message object with the ERROR level.
+     *
+     * @param message the message object to log.
+     */
+    void error(Object message);
+
+    /**
+     * Logs a message with parameters at the ERROR level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param params parameters to the message.
+     */
+    void error(String message, Object... params);
+
+    /**
+     * Logs a message with parameters which are only to be constructed if the
+     * logging level is the ERROR level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param paramSuppliers An array of functions, which when called, produce
+     *        the desired log message parameters.
+     */
+    void error(String message, Supplier<?>... paramSuppliers);
+
+    /**
+     * Logs a message at the ERROR level including the stack trace of the {@link Throwable}
+     * <code>t</code> passed as parameter.
+     *
+     * @param message the message object to log.
+     * @param t the exception to log, including its stack trace.
+     */
+    void error(String message, Throwable t);
+
+    /**
+     * Logs a message object with the FATAL level.
+     *
+     * @param message the message string to log.
+     */
+    void fatal(String message);
+
+    /**
+     * Logs a message object with the FATAL level.
+     *
+     * @param message the message object to log.
+     */
+    void fatal(Object message);
+
+    /**
+     * Logs a message with parameters at the FATAL level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param params parameters to the message.
+     */
+    void fatal(String message, Object... params);
+
+    /**
+     * Logs a message with parameters which are only to be constructed if the
+     * logging level is the FATAL level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param paramSuppliers An array of functions, which when called, produce
+     *        the desired log message parameters.
+     */
+    void fatal(String message, Supplier<?>... paramSuppliers);
+
+    /**
+     * Logs a message at the FATAL level including the stack trace of the {@link Throwable}
+     * <code>t</code> passed as parameter.
+     *
+     * @param message the message object to log.
+     * @param t the exception to log, including its stack trace.
+     */
+    void fatal(String message, Throwable t);
+
+    /**
+     * Gets the logger name.
+     *
+     * @return the logger name.
+     */
+    String getName();
+
+    /**
+     * Logs a message object with the INFO level.
+     *
+     * @param message the message string to log.
+     */
+    void info(String message);
+
+    /**
+     * Logs a message object with the INFO level.
+     *
+     * @param message the message object to log.
+     */
+    void info(Object message);
+
+    /**
+     * Logs a message with parameters at the INFO level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param params parameters to the message.
+     */
+    void info(String message, Object... params);
+
+    /**
+     * Logs a message with parameters which are only to be constructed if the
+     * logging level is the INFO level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param paramSuppliers An array of functions, which when called, produce
+     *        the desired log message parameters.
+     */
+    void info(String message, Supplier<?>... paramSuppliers);
+
+    /**
+     * Logs a message at the INFO level including the stack trace of the {@link Throwable}
+     * <code>t</code> passed as parameter.
+     *
+     * @param message the message object to log.
+     * @param t the exception to log, including its stack trace.
+     */
+    void info(String message, Throwable t);
+
+    /**
+     * Checks whether this Logger is enabled for the DEBUG Level.
+     *
+     * @return boolean - {@code true} if this Logger is enabled for level DEBUG, {@code false}
+     *         otherwise.
+     */
+    boolean isDebugEnabled();
+
+    /**
+     * Checks whether this Logger is enabled for the ERROR Level.
+     *
+     * @return boolean - {@code true} if this Logger is enabled for level ERROR, {@code false}
+     *         otherwise.
+     */
+    boolean isErrorEnabled();
+
+    /**
+     * Checks whether this Logger is enabled for the FATAL Level.
+     *
+     * @return boolean - {@code true} if this Logger is enabled for level FATAL, {@code false}
+     *         otherwise.
+     */
+    boolean isFatalEnabled();
+
+    /**
+     * Checks whether this Logger is enabled for the INFO Level.
+     *
+     * @return boolean - {@code true} if this Logger is enabled for level INFO, {@code false}
+     *         otherwise.
+     */
+    boolean isInfoEnabled();
+
+    /**
+     * Checks whether this Logger is enabled for the TRACE level.
+     *
+     * @return boolean - {@code true} if this Logger is enabled for level TRACE, {@code false}
+     *         otherwise.
+     */
+    boolean isTraceEnabled();
+
+    /**
+     * Checks whether this Logger is enabled for the WARN Level.
+     *
+     * @return boolean - {@code true} if this Logger is enabled for level WARN, {@code false}
+     *         otherwise.
+     */
+    boolean isWarnEnabled();
+
+    /**
+     * Logs a message object with the TRACE level.
+     *
+     * @param message the message string to log.
+     */
+    void trace(String message);
+
+    /**
+     * Logs a message object with the TRACE level.
+     *
+     * @param message the message object to log.
+     */
+    void trace(Object message);
+
+    /**
+     * Logs a message with parameters at the TRACE level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param params parameters to the message.
+     * @see #getMessageFactory()
+     */
+    void trace(String message, Object... params);
+
+    /**
+     * Logs a message with parameters which are only to be constructed if the
+     * logging level is the TRACE level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param paramSuppliers An array of functions, which when called, produce
+     *        the desired log message parameters.
+     */
+    void trace(String message, Supplier<?>... paramSuppliers);
+
+    /**
+     * Logs a message at the TRACE level including the stack trace of the {@link Throwable}
+     * <code>t</code> passed as parameter.
+     *
+     * @param message the message object to log.
+     * @param t the exception to log, including its stack trace.
+     * @see #debug(String)
+     */
+    void trace(String message, Throwable t);
+
+    /**
+     * Logs a message object with the WARN level.
+     *
+     * @param message the message string to log.
+     */
+    void warn(String message);
+
+    /**
+     * Logs a message object with the WARN level.
+     *
+     * @param message the message object to log.
+     */
+    void warn(Object message);
+
+    /**
+     * Logs a message with parameters at the WARN level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param params parameters to the message.
+     * @see #getMessageFactory()
+     */
+    void warn(String message, Object... params);
+
+    /**
+     * Logs a message with parameters which are only to be constructed if the
+     * logging level is the WARN level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param paramSuppliers An array of functions, which when called, produce
+     *        the desired log message parameters.
+     */
+    void warn(String message, Supplier<?>... paramSuppliers);
+
+    /**
+     * Logs a message at the WARN level including the stack trace of the {@link Throwable}
+     * <code>t</code> passed as parameter.
+     *
+     * @param message the message object to log.
+     * @param t the exception to log, including its stack trace.
+     */
+    void warn(String message, Throwable t);
+}
\ No newline at end of file
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/log/Log4j2Factory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/log/Log4j2Factory.java
new file mode 100644
index 0000000..ef6fdbc
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/log/Log4j2Factory.java
@@ -0,0 +1,88 @@
+package org.apache.commons.jcs3.log;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.message.MessageFactory;
+import org.apache.logging.log4j.message.MessageFormatMessageFactory;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+/**
+ * This is a SPI factory implementation for log4j2
+ */
+public class Log4j2Factory implements LogFactory
+{
+    /** Use java.text.MessageFormat for log messages */
+    private final MessageFactory messageFactory = new MessageFormatMessageFactory();
+
+    /**
+     * Return the name of the Log subsystem managed by this factory
+     *
+     * @return the name of the log subsystem
+     */
+    @Override
+    public String getName()
+    {
+        return "log4j2";
+    }
+
+    /**
+     * Shutdown the logging system if the logging system supports it.
+     */
+    @Override
+    public void shutdown()
+    {
+        org.apache.logging.log4j.LogManager.shutdown();
+    }
+
+    /**
+     * Returns a Log using the fully qualified name of the Class as the Log
+     * name.
+     *
+     * @param clazz
+     *            The Class whose name should be used as the Log name. If null
+     *            it will default to the calling class.
+     * @return The Log.
+     * @throws UnsupportedOperationException
+     *             if {@code clazz} is {@code null} and the calling class cannot
+     *             be determined.
+     */
+    @Override
+    public Log getLog(final Class<?> clazz)
+    {
+        Logger logger = org.apache.logging.log4j.LogManager.getLogger(clazz, messageFactory);
+        return new Log4j2LogAdapter(logger);
+    }
+
+    /**
+     * Returns a Log with the specified name.
+     *
+     * @param name
+     *            The logger name. If null the name of the calling class will be
+     *            used.
+     * @return The Log.
+     * @throws UnsupportedOperationException
+     *             if {@code name} is {@code null} and the calling class cannot
+     *             be determined.
+     */
+    @Override
+    public Log getLog(final String name)
+    {
+        Logger logger = org.apache.logging.log4j.LogManager.getLogger(name, messageFactory);
+        return new Log4j2LogAdapter(logger);
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/log/Log4j2LogAdapter.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/log/Log4j2LogAdapter.java
new file mode 100644
index 0000000..60239b7
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/log/Log4j2LogAdapter.java
@@ -0,0 +1,531 @@
+package org.apache.commons.jcs3.log;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+import java.util.function.Supplier;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * This is a wrapper around the <code>org.apache.logging.log4j.Logger</code> implementing our own
+ * <code>Log</code> interface.
+ */
+public class Log4j2LogAdapter implements Log
+{
+    private final Logger logger;
+
+    /**
+     * Construct a Log4j Logger wrapper
+     *
+     * @param logger the log4j Logger
+     */
+    public Log4j2LogAdapter(Logger logger)
+    {
+        super();
+        this.logger = logger;
+    }
+
+    private void log(Level level, String message, Supplier<?>... paramSuppliers)
+    {
+        if (logger.isEnabled(level))
+        {
+            if (paramSuppliers == null)
+            {
+                logger.log(level, message);
+            }
+            else
+            {
+                switch (paramSuppliers.length)
+                {
+                    case 1: logger.log(level, message, paramSuppliers[0].get());
+                            break;
+                    case 2: logger.log(level, message, paramSuppliers[0].get(),
+                            paramSuppliers[1].get());
+                            break;
+                    case 3: logger.log(level, message, paramSuppliers[0].get(),
+                            paramSuppliers[1].get(), paramSuppliers[2].get());
+                            break;
+                    case 4: logger.log(level, message, paramSuppliers[0].get(),
+                            paramSuppliers[1].get(), paramSuppliers[2].get(),
+                            paramSuppliers[3].get());
+                            break;
+                    case 5: logger.log(level, message, paramSuppliers[0].get(),
+                            paramSuppliers[1].get(), paramSuppliers[2].get(),
+                            paramSuppliers[3].get(), paramSuppliers[4].get());
+                            break;
+                    default: logger.log(level, message, paramSuppliers[0].get(),
+                            paramSuppliers[1].get(), paramSuppliers[2].get(),
+                            paramSuppliers[3].get(), paramSuppliers[4].get(),
+                            paramSuppliers[5].get());
+                            break;
+                }
+            }
+        }
+    }
+
+    /**
+     * Logs a message object with the DEBUG level.
+     *
+     * @param message the message string to log.
+     */
+    @Override
+    public void debug(String message)
+    {
+        logger.debug(message);
+    }
+
+    /**
+     * Logs a message object with the DEBUG level.
+     *
+     * @param message the message object to log.
+     */
+    @Override
+    public void debug(Object message)
+    {
+        logger.debug(message);
+    }
+
+    /**
+     * Logs a message with parameters at the DEBUG level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param params parameters to the message.
+     */
+    @Override
+    public void debug(String message, Object... params)
+    {
+        logger.debug(message, params);
+    }
+
+    /**
+     * Logs a message with parameters which are only to be constructed if the
+     * logging level is the DEBUG level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param paramSuppliers An array of functions, which when called, produce
+     *        the desired log message parameters.
+     */
+    @Override
+    public void debug(String message, Supplier<?>... paramSuppliers)
+    {
+        log(Level.DEBUG, message, paramSuppliers);
+    }
+
+    /**
+     * Logs a message at the DEBUG level including the stack trace of the {@link Throwable}
+     * <code>t</code> passed as parameter.
+     *
+     * @param message the message to log.
+     * @param t the exception to log, including its stack trace.
+     */
+    @Override
+    public void debug(String message, Throwable t)
+    {
+        logger.debug(message, t);
+    }
+
+    /**
+     * Logs a message object with the ERROR level.
+     *
+     * @param message the message string to log.
+     */
+    @Override
+    public void error(String message)
+    {
+        logger.error(message);
+    }
+
+    /**
+     * Logs a message object with the ERROR level.
+     *
+     * @param message the message object to log.
+     */
+    @Override
+    public void error(Object message)
+    {
+        logger.error(message);
+    }
+
+    /**
+     * Logs a message with parameters at the ERROR level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param params parameters to the message.
+     */
+    @Override
+    public void error(String message, Object... params)
+    {
+        logger.error(message, params);
+    }
+
+    /**
+     * Logs a message with parameters which are only to be constructed if the
+     * logging level is the ERROR level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param paramSuppliers An array of functions, which when called, produce
+     *        the desired log message parameters.
+     */
+    @Override
+    public void error(String message, Supplier<?>... paramSuppliers)
+    {
+        log(Level.ERROR, message, paramSuppliers);
+    }
+
+    /**
+     * Logs a message at the ERROR level including the stack trace of the {@link Throwable}
+     * <code>t</code> passed as parameter.
+     *
+     * @param message the message object to log.
+     * @param t the exception to log, including its stack trace.
+     */
+    @Override
+    public void error(String message, Throwable t)
+    {
+        logger.error(message, t);
+    }
+
+    /**
+     * Logs a message object with the FATAL level.
+     *
+     * @param message the message string to log.
+     */
+    @Override
+    public void fatal(String message)
+    {
+        logger.fatal(message);
+    }
+
+    /**
+     * Logs a message object with the FATAL level.
+     *
+     * @param message the message object to log.
+     */
+    @Override
+    public void fatal(Object message)
+    {
+        logger.fatal(message);
+    }
+
+    /**
+     * Logs a message with parameters at the FATAL level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param params parameters to the message.
+     */
+    @Override
+    public void fatal(String message, Object... params)
+    {
+        logger.fatal(message, params);
+    }
+
+    /**
+     * Logs a message with parameters which are only to be constructed if the
+     * logging level is the FATAL level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param paramSuppliers An array of functions, which when called, produce
+     *        the desired log message parameters.
+     */
+    @Override
+    public void fatal(String message, Supplier<?>... paramSuppliers)
+    {
+        log(Level.FATAL, message, paramSuppliers);
+    }
+
+    /**
+     * Logs a message at the FATAL level including the stack trace of the {@link Throwable}
+     * <code>t</code> passed as parameter.
+     *
+     * @param message the message object to log.
+     * @param t the exception to log, including its stack trace.
+     */
+    @Override
+    public void fatal(String message, Throwable t)
+    {
+        logger.fatal(message, t);
+    }
+
+    /**
+     * Gets the logger name.
+     *
+     * @return the logger name.
+     */
+    @Override
+    public String getName()
+    {
+        return logger.getName();
+    }
+
+    /**
+     * Logs a message object with the INFO level.
+     *
+     * @param message the message string to log.
+     */
+    @Override
+    public void info(String message)
+    {
+        logger.info(message);
+    }
+
+    /**
+     * Logs a message object with the INFO level.
+     *
+     * @param message the message object to log.
+     */
+    @Override
+    public void info(Object message)
+    {
+        logger.info(message);
+    }
+
+    /**
+     * Logs a message with parameters at the INFO level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param params parameters to the message.
+     */
+    @Override
+    public void info(String message, Object... params)
+    {
+        logger.info(message, params);
+    }
+
+    /**
+     * Logs a message with parameters which are only to be constructed if the
+     * logging level is the INFO level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param paramSuppliers An array of functions, which when called, produce
+     *        the desired log message parameters.
+     */
+    @Override
+    public void info(String message, Supplier<?>... paramSuppliers)
+    {
+        log(Level.INFO, message, paramSuppliers);
+    }
+
+    /**
+     * Logs a message at the INFO level including the stack trace of the {@link Throwable}
+     * <code>t</code> passed as parameter.
+     *
+     * @param message the message object to log.
+     * @param t the exception to log, including its stack trace.
+     */
+    @Override
+    public void info(String message, Throwable t)
+    {
+        logger.info(message, t);
+    }
+
+    /**
+     * Checks whether this Logger is enabled for the DEBUG Level.
+     *
+     * @return boolean - {@code true} if this Logger is enabled for level DEBUG, {@code false}
+     *         otherwise.
+     */
+    @Override
+    public boolean isDebugEnabled()
+    {
+        return logger.isDebugEnabled();
+    }
+
+    /**
+     * Checks whether this Logger is enabled for the ERROR Level.
+     *
+     * @return boolean - {@code true} if this Logger is enabled for level ERROR, {@code false}
+     *         otherwise.
+     */
+    @Override
+    public boolean isErrorEnabled()
+    {
+        return logger.isErrorEnabled();
+    }
+
+    /**
+     * Checks whether this Logger is enabled for the FATAL Level.
+     *
+     * @return boolean - {@code true} if this Logger is enabled for level FATAL, {@code false}
+     *         otherwise.
+     */
+    @Override
+    public boolean isFatalEnabled()
+    {
+        return logger.isFatalEnabled();
+    }
+
+    /**
+     * Checks whether this Logger is enabled for the INFO Level.
+     *
+     * @return boolean - {@code true} if this Logger is enabled for level INFO, {@code false}
+     *         otherwise.
+     */
+    @Override
+    public boolean isInfoEnabled()
+    {
+        return logger.isInfoEnabled();
+    }
+
+    /**
+     * Checks whether this Logger is enabled for the TRACE level.
+     *
+     * @return boolean - {@code true} if this Logger is enabled for level TRACE, {@code false}
+     *         otherwise.
+     */
+    @Override
+    public boolean isTraceEnabled()
+    {
+        return logger.isTraceEnabled();
+    }
+
+    /**
+     * Checks whether this Logger is enabled for the WARN Level.
+     *
+     * @return boolean - {@code true} if this Logger is enabled for level WARN, {@code false}
+     *         otherwise.
+     */
+    @Override
+    public boolean isWarnEnabled()
+    {
+        return logger.isWarnEnabled();
+    }
+
+    /**
+     * Logs a message object with the TRACE level.
+     *
+     * @param message the message string to log.
+     */
+    @Override
+    public void trace(String message)
+    {
+        logger.trace(message);
+    }
+
+    /**
+     * Logs a message object with the TRACE level.
+     *
+     * @param message the message object to log.
+     */
+    @Override
+    public void trace(Object message)
+    {
+        logger.trace(message);
+    }
+
+    /**
+     * Logs a message with parameters at the TRACE level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param params parameters to the message.
+     */
+    @Override
+    public void trace(String message, Object... params)
+    {
+        logger.trace(message, params);
+    }
+
+    /**
+     * Logs a message with parameters which are only to be constructed if the
+     * logging level is the TRACE level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param paramSuppliers An array of functions, which when called, produce
+     *        the desired log message parameters.
+     */
+    @Override
+    public void trace(String message, Supplier<?>... paramSuppliers)
+    {
+        log(Level.TRACE, message, paramSuppliers);
+    }
+
+    /**
+     * Logs a message at the TRACE level including the stack trace of the {@link Throwable}
+     * <code>t</code> passed as parameter.
+     *
+     * @param message the message object to log.
+     * @param t the exception to log, including its stack trace.
+     * @see #debug(String)
+     */
+    @Override
+    public void trace(String message, Throwable t)
+    {
+        logger.trace(message, t);
+    }
+
+    /**
+     * Logs a message object with the WARN level.
+     *
+     * @param message the message string to log.
+     */
+    @Override
+    public void warn(String message)
+    {
+        logger.warn(message);
+    }
+
+    /**
+     * Logs a message object with the WARN level.
+     *
+     * @param message the message object to log.
+     */
+    @Override
+    public void warn(Object message)
+    {
+        logger.warn(message);
+    }
+
+    /**
+     * Logs a message with parameters at the WARN level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param params parameters to the message.
+     */
+    @Override
+    public void warn(String message, Object... params)
+    {
+        logger.warn(message, params);
+    }
+
+    /**
+     * Logs a message with parameters which are only to be constructed if the
+     * logging level is the WARN level.
+     *
+     * @param message the message to log; the format depends on the message factory.
+     * @param paramSuppliers An array of functions, which when called, produce
+     *        the desired log message parameters.
+     */
+    @Override
+    public void warn(String message, Supplier<?>... paramSuppliers)
+    {
+        log(Level.WARN, message, paramSuppliers);
+    }
+
+    /**
+     * Logs a message at the WARN level including the stack trace of the {@link Throwable}
+     * <code>t</code> passed as parameter.
+     *
+     * @param message the message object to log.
+     * @param t the exception to log, including its stack trace.
+     */
+    @Override
+    public void warn(String message, Throwable t)
+    {
+        logger.warn(message, t);
+    }
+}
\ No newline at end of file
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/log/LogFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/log/LogFactory.java
new file mode 100644
index 0000000..fbecd24
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/log/LogFactory.java
@@ -0,0 +1,68 @@
+package org.apache.commons.jcs3.log;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+/**
+ * This is a SPI factory interface for specialized Log objects
+ */
+public interface LogFactory
+{
+    /**
+     * The name of the root Log.
+     */
+    String ROOT_LOGGER_NAME = "";
+
+    /**
+     * Return the name of the Log subsystem managed by this factory
+     *
+     * @return the name of the log subsystem
+     */
+    String getName();
+
+    /**
+     * Shutdown the logging system if the logging system supports it.
+     */
+    void shutdown();
+
+    /**
+     * Returns a Log using the fully qualified name of the Class as the Log
+     * name.
+     *
+     * @param clazz
+     *            The Class whose name should be used as the Log name. If null
+     *            it will default to the calling class.
+     * @return The Log.
+     * @throws UnsupportedOperationException
+     *             if {@code clazz} is {@code null} and the calling class cannot
+     *             be determined.
+     */
+    Log getLog(final Class<?> clazz);
+
+    /**
+     * Returns a Log with the specified name.
+     *
+     * @param name
+     *            The logger name. If null the name of the calling class will be
+     *            used.
+     * @return The Log.
+     * @throws UnsupportedOperationException
+     *             if {@code name} is {@code null} and the calling class cannot
+     *             be determined.
+     */
+    Log getLog(final String name);
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/log/LogManager.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/log/LogManager.java
new file mode 100644
index 0000000..c5875f3
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/log/LogManager.java
@@ -0,0 +1,139 @@
+package org.apache.commons.jcs3.log;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+import java.util.ServiceLoader;
+
+/**
+ * This is a borrowed and stripped-down version of the log4j2 LogManager class.
+ *
+ * The anchor point for the JCS logging system. The most common usage of this
+ * class is to obtain a named {@link Log}.
+ */
+public class LogManager
+{
+    /**
+     * The name of log subsystem
+     */
+    private static String logSystem = null;
+
+    /**
+     * The SPI LogFactory
+     */
+    private static class LogFactoryHolder
+    {
+        static final LogFactory INSTANCE = createLogFactory();
+
+        /**
+         * Scans the classpath to find a logging implementation.
+         *
+         * @return the LogFactory
+         * @throws RuntimeException, if no factory implementation could be found
+         */
+        private static LogFactory createLogFactory()
+        {
+            ServiceLoader<LogFactory> factories = ServiceLoader.load(LogFactory.class);
+            if (LogManager.logSystem == null)
+            {
+                LogManager.logSystem = System.getProperty("jcs.logSystem", "jul");
+            }
+
+            for (LogFactory factory : factories)
+            {
+                if (logSystem.equalsIgnoreCase(factory.getName()))
+                {
+                    return factory;
+                }
+            }
+
+            throw new RuntimeException("Could not find factory implementation for log subsystem " + logSystem);
+        }
+    }
+
+    /**
+     * Set the log system. Must be called before getLog is called
+     *
+     * @param logSystem the logSystem to set
+     */
+    public static void setLogSystem(String logSystem)
+    {
+        LogManager.logSystem = logSystem;
+    }
+
+    /**
+     * Return the LogFactory
+     */
+    private static LogFactory getLogFactory()
+    {
+        return LogFactoryHolder.INSTANCE;
+    }
+
+    /**
+     * Prevents instantiation
+     */
+    protected LogManager()
+    {
+    }
+
+    /**
+     * Shutdown the logging system if the logging system supports it.
+     */
+    public static void shutdown()
+    {
+        getLogFactory().shutdown();
+    }
+
+    /**
+     * Returns a Log using the fully qualified name of the Class as the Log
+     * name.
+     *
+     * @param clazz
+     *            The Class whose name should be used as the Log name.
+     * @return The Log.
+     * @throws UnsupportedOperationException
+     *             if {@code clazz} is {@code null}
+     */
+    public static Log getLog(final Class<?> clazz)
+    {
+        return getLogFactory().getLog(clazz);
+    }
+
+    /**
+     * Returns a Log with the specified name.
+     *
+     * @param name
+     *            The logger name.
+     * @return The Log.
+     * @throws UnsupportedOperationException
+     *             if {@code name} is {@code null}
+     */
+    public static Log getLog(final String name)
+    {
+        return getLogFactory().getLog(name);
+    }
+
+    /**
+     * Returns the root logger.
+     *
+     * @return the root logger, named {@link LogFactory.ROOT_LOGGER_NAME}.
+     */
+    public static Log getRootLogger()
+    {
+        return getLog(LogFactory.ROOT_LOGGER_NAME);
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/log/MessageFormatter.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/log/MessageFormatter.java
new file mode 100644
index 0000000..fbabfa9
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/log/MessageFormatter.java
@@ -0,0 +1,130 @@
+package org.apache.commons.jcs3.log;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.IllegalFormatException;
+import java.util.function.Supplier;
+
+/**
+ * Handles messages that consist of a format string conforming to
+ * java.text.MessageFormat. (Borrowed from log4j2)
+ */
+public class MessageFormatter
+{
+    private final String messagePattern;
+    private transient Object[] parameters;
+    private transient String formattedMessage;
+    private transient Throwable throwable;
+
+    /**
+     * Constructs a message formatter.
+     *
+     * @param messagePattern
+     *            the pattern for this message format
+     * @param parameters
+     *            The objects to format
+     */
+    public MessageFormatter(final String messagePattern, final Object... parameters)
+    {
+        this.messagePattern = messagePattern;
+        this.parameters = parameters;
+        final int length = parameters == null ? 0 : parameters.length;
+        if (length > 0 && parameters[length - 1] instanceof Throwable)
+        {
+            this.throwable = (Throwable) parameters[length - 1];
+        }
+    }
+
+    /**
+     * Constructs a message formatter.
+     *
+     * @param messagePattern
+     *            the pattern for this message format
+     * @param paramSuppliers
+     *            An array of functions, which when called, produce the desired
+     *            log message parameters.
+     */
+    public MessageFormatter(final String messagePattern, final Supplier<?>... paramSuppliers)
+    {
+        this.messagePattern = messagePattern;
+        this.parameters = Arrays.stream(paramSuppliers)
+                            .map(s -> s.get())
+                            .toArray();
+
+        final int length = parameters == null ? 0 : parameters.length;
+        if (length > 0 && parameters[length - 1] instanceof Throwable)
+        {
+            this.throwable = (Throwable) parameters[length - 1];
+        }
+    }
+
+    /**
+     * Returns the formatted message.
+     *
+     * @return the formatted message.
+     */
+    public String getFormattedMessage()
+    {
+        if (formattedMessage == null)
+        {
+            formattedMessage = formatMessage(messagePattern, parameters);
+        }
+        return formattedMessage;
+    }
+
+    protected String formatMessage(final String msgPattern, final Object... args)
+    {
+        try
+        {
+            final MessageFormat temp = new MessageFormat(msgPattern);
+            return temp.format(args);
+        }
+        catch (final IllegalFormatException ife)
+        {
+            return msgPattern;
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return getFormattedMessage();
+    }
+
+    /**
+     * Return the throwable passed to the Message.
+     *
+     * @return the Throwable.
+     */
+    public Throwable getThrowable()
+    {
+        return throwable;
+    }
+
+    /**
+     * Return true, if the parameters list contains a Throwable.
+     *
+     * @return true, if the parameters list contains a Throwable.
+     */
+    public boolean hasThrowable()
+    {
+        return throwable != null;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/package.html b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/package.html
similarity index 100%
rename from commons-jcs-core/src/main/java/org/apache/commons/jcs/package.html
rename to commons-jcs-core/src/main/java/org/apache/commons/jcs3/package.html
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/access/AbstractJCSWorkerHelper.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/access/AbstractJCSWorkerHelper.java
new file mode 100644
index 0000000..f27a43a
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/access/AbstractJCSWorkerHelper.java
@@ -0,0 +1,60 @@
+package org.apache.commons.jcs3.utils.access;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * This is an abstract template for JCSWorkerHelper implementations. it simple has a convenience
+ * method for setting the finished flag.
+ * <p>
+ * @author tsavo
+ */
+public abstract class AbstractJCSWorkerHelper<V> implements JCSWorkerHelper<V>
+{
+    /** finished flag. Can't we use wait notify? */
+    private final AtomicBoolean finished = new AtomicBoolean(false);
+
+    /**
+     * Default
+     */
+    public AbstractJCSWorkerHelper()
+    {
+        super();
+    }
+
+    /**
+     * @return finished
+     */
+    @Override
+    public boolean isFinished()
+    {
+        return finished.get();
+    }
+
+    /**
+     * @param isFinished
+     */
+    @Override
+    public void setFinished( boolean isFinished )
+    {
+        finished.set(isFinished);
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/access/JCSWorker.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/access/JCSWorker.java
new file mode 100644
index 0000000..f0caba8
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/access/JCSWorker.java
@@ -0,0 +1,286 @@
+package org.apache.commons.jcs3.utils.access;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.access.GroupCacheAccess;
+import org.apache.commons.jcs3.access.exception.CacheException;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * Utility class to encapsulate doing a piece of work, and caching the results
+ * in JCS. Simply construct this class with the region name for the Cache and
+ * keep a static reference to it instead of the JCS itself. Then make a new
+ * org.apache.commons.jcs3.utils.access.AbstractJCSWorkerHelper and implement Object
+ * doWork() and do the work in there, returning the object to be cached. Then
+ * call .getResult() with the key and the AbstractJCSWorkerHelper to get the
+ * result of the work. If the object isn't already in the Cache,
+ * AbstractJCSWorkerHelper.doWork() will get called, and the result will be put
+ * into the cache. If the object is already in cache, the cached result will be
+ * returned instead.
+ * <p>
+ * As an added bonus, multiple JCSWorkers with the same region, and key won't do
+ * the work multiple times: The first JCSWorker to get started will do the work,
+ * and all subsequent workers with the same region, group, and key will wait on
+ * the first one and use his resulting work instead of doing the work
+ * themselves.
+ * <p>
+ * This is ideal when the work being done is a query to the database where the
+ * results may take time to be retrieved.
+ * <p>
+ * For example:
+ *
+ * <pre>
+ *      public static JCSWorker cachingWorker = new JCSWorker(&quot;example region&quot;);
+ *   		public Object getSomething(Serializable aKey){
+ *        JCSWorkerHelper helper = new AbstractJCSWorkerHelper(){
+ *          public Object doWork(){
+ *            // Do some (DB?) work here which results in a list
+ *            // This only happens if the cache dosn't have a item in this region for aKey
+ *            // Note this is especially useful with Hibernate, which will cache indiviual
+ *            // Objects, but not entire query result sets.
+ *            List results = query.list();
+ *            // Whatever we return here get's cached with aKey, and future calls to
+ *            // getResult() on a CachedWorker with the same region and key will return that instead.
+ *            return results;
+ *        };
+ *        List result = worker.getResult(aKey, helper);
+ *      }
+ * </pre>
+ *
+ * This is essentially the same as doing:
+ *
+ * <pre>
+ * JCS jcs = JCS.getInstance( &quot;exampleregion&quot; );
+ * List results = (List) jcs.get( aKey );
+ * if ( results != null )
+ * {
+ *     //do the work here
+ *     results = query.list();
+ *     jcs.put( aKey, results );
+ * }
+ * </pre>
+ *
+ * <p>
+ * But has the added benefit of the work-load sharing; under normal
+ * circumstances if multiple threads all tried to do the same query at the same
+ * time, the same query would happen multiple times on the database, and the
+ * resulting object would get put into JCS multiple times.
+ * <p>
+ * @author Travis Savo
+ */
+public class JCSWorker<K, V>
+{
+    /** The logger */
+    private static final Log logger = LogManager.getLog( JCSWorker.class );
+
+    /** The cache we are working with */
+    private CacheAccess<K, V> cache;
+
+    /** The cache we are working with */
+    private GroupCacheAccess<K, V> groupCache;
+
+    /**
+     * Map to hold who's doing work presently.
+     */
+    private volatile ConcurrentMap<String, JCSWorkerHelper<V>> map = new ConcurrentHashMap<>();
+
+    /**
+     * Region for the JCS cache.
+     */
+    private final String region;
+
+    /**
+     * Constructor which takes a region for the JCS cache.
+     * @param aRegion
+     *            The Region to use for the JCS cache.
+     */
+    public JCSWorker( final String aRegion )
+    {
+        region = aRegion;
+        try
+        {
+            cache = JCS.getInstance( aRegion );
+            groupCache = JCS.getGroupCacheInstance( aRegion );
+        }
+        catch ( CacheException e )
+        {
+            throw new RuntimeException( e.getMessage() );
+        }
+    }
+
+    /**
+     * Getter for the region of the JCS Cache.
+     * @return The JCS region in which the result will be cached.
+     */
+    public String getRegion()
+    {
+        return region;
+    }
+
+    /**
+     * Gets the cached result for this region/key OR does the work and caches
+     * the result, returning the result. If the result has not been cached yet,
+     * this calls doWork() on the JCSWorkerHelper to do the work and cache the
+     * result. This is also an opportunity to do any post processing of the
+     * result in your CachedWorker implementation.
+     * @param aKey
+     *            The key to get/put with on the Cache.
+     * @param aWorker
+     *            The JCSWorkerHelper implementing Object doWork(). This gets
+     *            called if the cache get misses, and the result is put into
+     *            cache.
+     * @return The result of doing the work, or the cached result.
+     * @throws Exception
+     *             Throws an exception if anything goes wrong while doing the
+     *             work.
+     */
+    public V getResult( K aKey, JCSWorkerHelper<V> aWorker )
+        throws Exception
+    {
+        return run( aKey, null, aWorker );
+    }
+
+    /**
+     * Gets the cached result for this region/key OR does the work and caches
+     * the result, returning the result. If the result has not been cached yet,
+     * this calls doWork() on the JCSWorkerHelper to do the work and cache the
+     * result. This is also an opportunity to do any post processing of the
+     * result in your CachedWorker implementation.
+     * @param aKey
+     *            The key to get/put with on the Cache.
+     * @param aGroup
+     *            The cache group to put the result in.
+     * @param aWorker
+     *            The JCSWorkerHelper implementing Object doWork(). This gets
+     *            called if the cache get misses, and the result is put into
+     *            cache.
+     * @return The result of doing the work, or the cached result.
+     * @throws Exception
+     *             Throws an exception if anything goes wrong while doing the
+     *             work.
+     */
+    public V getResult( K aKey, String aGroup, JCSWorkerHelper<V> aWorker )
+        throws Exception
+    {
+        return run( aKey, aGroup, aWorker );
+    }
+
+    /**
+     * Try and get the object from the cache, and if it's not there, do the work
+     * and cache it. This also ensures that only one CachedWorker is doing the
+     * work and subsequent calls to a CachedWorker with identical
+     * region/key/group will wait on the results of this call. It will call the
+     * JCSWorkerHelper.doWork() if the cache misses, and will put the result.
+     * @param aKey
+     * @param aGroup
+     * @param aHelper
+     * @return Either the result of doing the work, or the cached result.
+     * @throws Exception
+     *             If something goes wrong while doing the work, throw an
+     *             exception.
+     */
+    private V run( K aKey, String aGroup, JCSWorkerHelper<V> aHelper )
+        throws Exception
+    {
+        V result = null;
+        // long start = 0;
+        // long dbTime = 0;
+        JCSWorkerHelper<V> helper = map.putIfAbsent(getRegion() + aKey, aHelper);
+
+        if ( helper != null )
+        {
+            synchronized ( helper )
+            {
+                logger.debug( "Found a worker already doing this work ({0}:{1}).",
+                        () -> getRegion(), () -> aKey );
+                while ( !helper.isFinished() )
+                {
+                    try
+                    {
+                        helper.wait();
+                    }
+                    catch (InterruptedException e)
+                    {
+                        // expected
+                    }
+                }
+                logger.debug( "Another thread finished our work for us. Using "
+                        + "those results instead. ({0}:{1}).",
+                        () -> getRegion(), () -> aKey );
+            }
+        }
+        // Do the work
+        try
+        {
+            logger.debug( "{0} is doing the work.", () -> getRegion() );
+
+            // Try to get the item from the cache
+            if ( aGroup != null )
+            {
+                result = groupCache.getFromGroup( aKey, aGroup );
+            }
+            else
+            {
+                result = cache.get( aKey );
+            }
+            // If the cache dosn't have it, do the work.
+            if ( result == null )
+            {
+                result = aHelper.doWork();
+                logger.debug( "Work Done, caching: key:{0}, group:{1}, result:{2}.",
+                        aKey, aGroup, result );
+                // Stick the result of the work in the cache.
+                if ( aGroup != null )
+                {
+                    groupCache.putInGroup( aKey, aGroup, result );
+                }
+                else
+                {
+                    cache.put( aKey, result );
+                }
+            }
+            // return the result
+            return result;
+        }
+        finally
+        {
+            logger.debug( "{0}:{1} entered finally.", () -> getRegion(),
+                    () -> aKey );
+
+            // Remove ourselves as the worker.
+            if ( helper == null )
+            {
+                map.remove( getRegion() + aKey );
+            }
+            synchronized ( aHelper )
+            {
+                aHelper.setFinished( true );
+                // Wake everyone waiting on us
+                aHelper.notifyAll();
+            }
+        }
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/access/JCSWorkerHelper.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/access/JCSWorkerHelper.java
new file mode 100644
index 0000000..12d62d4
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/access/JCSWorkerHelper.java
@@ -0,0 +1,60 @@
+package org.apache.commons.jcs3.utils.access;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Interface for doing a piece of work which is expected to be cached. This is
+ * meant to be used in conjunction with JCSWorker.
+ * <p>
+ * Implement doWork() to return the work being done. isFinished() should return
+ * false until setFinished(true) is called, after which time it should return
+ * true.
+ * <p>
+ * @author tsavo
+ */
+public interface JCSWorkerHelper<V>
+{
+    /**
+     * Tells us whether or not the work has been completed. This will be called
+     * automatically by JCSWorker. You should not call it yourself.
+     * <p>
+     * @return True if the work has already been done, otherwise false.
+     */
+    boolean isFinished();
+
+    /**
+     * Sets whether or not the work has been done.
+     * <p>
+     * @param isFinished
+     *            True if the work has already been done, otherwise false.
+     */
+    void setFinished( boolean isFinished );
+
+    /**
+     * The method to implement to do the work that should be cached. JCSWorker
+     * will call this itself! You should not call this directly.
+     * <p>
+     * @return The result of doing the work to be cached.
+     * @throws Exception
+     *             If anything goes wrong while doing the work, an Exception
+     *             should be thrown.
+     */
+    V doWork() throws Exception;
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/config/OptionConverter.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/config/OptionConverter.java
new file mode 100644
index 0000000..36f35f8
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/config/OptionConverter.java
@@ -0,0 +1,423 @@
+package org.apache.commons.jcs3.utils.config;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Properties;
+
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * This class is based on the log4j class org.apache.log4j.helpers.OptionConverter that was made by
+ * Ceki G&uuml;lc&uuml; Simon Kitching; Avy Sharell (sharell@online.fr) Anders Kristensen Matthieu
+ * Verbert (mve@zurich.ibm.com) A convenience class to convert property values to specific types.
+ */
+public class OptionConverter
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog( OptionConverter.class );
+
+    /** System property delimter */
+    private static final String DELIM_START = "${";
+
+    /** System property delimter */
+    private static final char DELIM_STOP = '}';
+
+    /** System property delimter start length */
+    private static final int DELIM_START_LEN = 2;
+
+    /** System property delimter end length */
+    private static final int DELIM_STOP_LEN = 1;
+
+    /** No instances please. */
+    private OptionConverter()
+    {
+        super();
+    }
+
+    /**
+     * Combines two arrays.
+     * @param l
+     * @param r
+     * @return String[]
+     */
+    public static String[] concatanateArrays( String[] l, String[] r )
+    {
+        int len = l.length + r.length;
+        String[] a = new String[len];
+
+        System.arraycopy( l, 0, a, 0, l.length );
+        System.arraycopy( r, 0, a, l.length, r.length );
+
+        return a;
+    }
+
+    /**
+     * Escapes special characters.
+     *
+     * @param s
+     * @return String
+     */
+    public static String convertSpecialChars( String s )
+    {
+        char c;
+        int len = s.length();
+        StringBuilder sb = new StringBuilder( len );
+
+        int i = 0;
+        while ( i < len )
+        {
+            c = s.charAt( i++ );
+            if ( c == '\\' )
+            {
+                c = s.charAt( i++ );
+                if ( c == 'n' )
+                {
+                    c = '\n';
+                }
+                else if ( c == 'r' )
+                {
+                    c = '\r';
+                }
+                else if ( c == 't' )
+                {
+                    c = '\t';
+                }
+                else if ( c == 'f' )
+                {
+                    c = '\f';
+                }
+                else if ( c == '\b' )
+                {
+                    c = '\b';
+                }
+                else if ( c == '\"' )
+                {
+                    c = '\"';
+                }
+                else if ( c == '\'' )
+                {
+                    c = '\'';
+                }
+                else if ( c == '\\' )
+                {
+                    c = '\\';
+                }
+            }
+            sb.append( c );
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Very similar to <code>System.getProperty</code> except that the {@link SecurityException} is
+     * hidden.
+     * @param key The key to search for.
+     * @param def The default value to return.
+     * @return the string value of the system property, or the default value if there is no property
+     *         with that key.
+     * @since 1.1
+     */
+
+    public static String getSystemProperty( String key, String def )
+    {
+        try
+        {
+            return System.getProperty( key, def );
+        }
+        catch ( Throwable e )
+        {
+            // MS-Java throws com.ms.security.SecurityExceptionEx
+            log.debug( "Was not allowed to read system property \"{0}\".", key );
+            return def;
+        }
+    }
+
+    /**
+     * Creates an object for the className value of the key.
+     *
+     * @param props
+     * @param key
+     * @param defaultValue
+     * @return Object that was created
+     */
+    public static <T> T instantiateByKey( Properties props, String key, T defaultValue )
+    {
+
+        // Get the value of the property in string form
+        String className = findAndSubst( key, props );
+        if ( className == null )
+        {
+            log.trace( "Could not find value for key {0}", key );
+            return defaultValue;
+        }
+        // Trim className to avoid trailing spaces that cause problems.
+        return OptionConverter.instantiateByClassName( className.trim(), defaultValue );
+    }
+
+    /**
+     * If <code>value</code> is "true", then <code>true</code> is returned. If <code>value</code> is
+     * "false", then <code>true</code> is returned. Otherwise, <code>default</code> is returned.
+     *
+     * Case of value is unimportant.
+     * @param value
+     * @param defaultValue
+     * @return Object
+     */
+    public static boolean toBoolean( String value, boolean defaultValue )
+    {
+        if ( value == null )
+        {
+            return defaultValue;
+        }
+        String trimmedVal = value.trim();
+        if ( "true".equalsIgnoreCase( trimmedVal ) )
+        {
+            return true;
+        }
+        if ( "false".equalsIgnoreCase( trimmedVal ) )
+        {
+            return false;
+        }
+        return defaultValue;
+    }
+
+    /**
+     * Converts to int.
+     *
+     * @param value
+     * @param defaultValue
+     * @return int
+     */
+    public static int toInt( String value, int defaultValue )
+    {
+        if ( value != null )
+        {
+            String s = value.trim();
+            try
+            {
+                return Integer.parseInt(s);
+            }
+            catch ( NumberFormatException e )
+            {
+                log.error( "[{0}] is not in proper int form.", s, e );
+            }
+        }
+        return defaultValue;
+    }
+
+    /**
+     * @param value
+     * @param defaultValue
+     * @return long
+     */
+    public static long toFileSize( String value, long defaultValue )
+    {
+        if ( value == null )
+        {
+            return defaultValue;
+        }
+
+        String s = value.trim().toUpperCase();
+        long multiplier = 1;
+        int index;
+
+        if ( ( index = s.indexOf( "KB" ) ) != -1 )
+        {
+            multiplier = 1024;
+            s = s.substring( 0, index );
+        }
+        else if ( ( index = s.indexOf( "MB" ) ) != -1 )
+        {
+            multiplier = 1024 * 1024;
+            s = s.substring( 0, index );
+        }
+        else if ( ( index = s.indexOf( "GB" ) ) != -1 )
+        {
+            multiplier = 1024 * 1024 * 1024;
+            s = s.substring( 0, index );
+        }
+        if ( s != null )
+        {
+            try
+            {
+                return Long.parseLong(s) * multiplier;
+            }
+            catch ( NumberFormatException e )
+            {
+                log.error( "[{0}] is not in proper int form.", s);
+                log.error( "[{0}] not in expected format", value, e );
+            }
+        }
+        return defaultValue;
+    }
+
+    /**
+     * Find the value corresponding to <code>key</code> in <code>props</code>. Then perform variable
+     * substitution on the found value.
+     *
+     * @param key
+     * @param props
+     * @return substituted string
+     */
+
+    public static String findAndSubst( String key, Properties props )
+    {
+        String value = props.getProperty( key );
+        if ( value == null )
+        {
+            return null;
+        }
+
+        try
+        {
+            return substVars( value, props );
+        }
+        catch ( IllegalArgumentException e )
+        {
+            log.error( "Bad option value [{0}]", value, e );
+            return value;
+        }
+    }
+
+    /**
+     * Instantiate an object given a class name. Check that the <code>className</code> is a subclass
+     * of <code>superClass</code>. If that test fails or the object could not be instantiated, then
+     * <code>defaultValue</code> is returned.
+     *
+     * @param className The fully qualified class name of the object to instantiate.
+     * @param defaultValue The object to return in case of non-fulfillment
+     * @return instantiated object
+     */
+
+    public static <T> T instantiateByClassName( String className, T defaultValue )
+    {
+        if ( className != null )
+        {
+            try
+            {
+                Class<?> classObj = Class.forName( className );
+                Object o = classObj.newInstance();
+
+                try
+                {
+                    @SuppressWarnings("unchecked") // CCE catched
+                    T t = (T) o;
+                    return t;
+                }
+                catch (ClassCastException e)
+                {
+                    log.error( "A \"{0}\" object is not assignable to the "
+                            + "generic variable.", className );
+                    return defaultValue;
+                }
+            }
+            catch ( ClassNotFoundException | InstantiationException | IllegalAccessException e )
+            {
+                log.error( "Could not instantiate class [{0}]", className, e );
+            }
+        }
+        return defaultValue;
+    }
+
+    /**
+     * Perform variable substitution in string <code>val</code> from the values of keys found in the
+     * system properties.
+     *
+     * The variable substitution delimiters are <b>${ </b> and <b>} </b>.
+     *
+     * For example, if the System properties contains "key=value", then the call
+     *
+     * <pre>
+     * String s = OptionConverter.substituteVars( &quot;Value of key is ${key}.&quot; );
+     * </pre>
+     *
+     * will set the variable <code>s</code> to "Value of key is value.".
+     *
+     * If no value could be found for the specified key, then the <code>props</code> parameter is
+     * searched, if the value could not be found there, then substitution defaults to the empty
+     * string.
+     *
+     * For example, if system properties contains no value for the key "inexistentKey", then the call
+     *
+     * <pre>
+     * String s = OptionConverter.subsVars( &quot;Value of inexistentKey is [${inexistentKey}]&quot; );
+     * </pre>
+     *
+     * will set <code>s</code> to "Value of inexistentKey is []"
+     * <p>
+     * An {@link java.lang.IllegalArgumentException}is thrown if <code>val</code> contains a start
+     * delimiter "${" which is not balanced by a stop delimiter "}".
+     * </p>
+     * <p>
+     * <b>Author </b> Avy Sharell
+     * </p>
+     * @param val The string on which variable substitution is performed.
+     * @param props
+     * @return String
+     * @throws IllegalArgumentException if <code>val</code> is malformed.
+     */
+
+    public static String substVars( String val, Properties props )
+        throws IllegalArgumentException
+    {
+        StringBuilder sbuf = new StringBuilder();
+
+        int i = 0;
+        int j;
+        int k;
+
+        while ( true )
+        {
+            j = val.indexOf( DELIM_START, i );
+            if ( j == -1 )
+            {
+                if ( i == 0 )
+                {
+                    return val;
+                }
+                sbuf.append( val.substring( i, val.length() ) );
+                return sbuf.toString();
+            }
+            sbuf.append( val.substring( i, j ) );
+            k = val.indexOf( DELIM_STOP, j );
+            if ( k == -1 )
+            {
+                throw new IllegalArgumentException( '"' + val + "\" has no closing brace. Opening brace at position "
+                    + j + '.' );
+            }
+            j += DELIM_START_LEN;
+            String key = val.substring( j, k );
+            // first try in System properties
+            String replacement = getSystemProperty( key, null );
+            // then try props parameter
+            if ( replacement == null && props != null )
+            {
+                replacement = props.getProperty( key );
+            }
+
+            if ( replacement != null )
+            {
+                sbuf.append( replacement );
+            }
+            i = k + DELIM_STOP_LEN;
+        }
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/config/PropertySetter.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/config/PropertySetter.java
new file mode 100644
index 0000000..e91ec93
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/config/PropertySetter.java
@@ -0,0 +1,299 @@
+package org.apache.commons.jcs3.utils.config;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.io.File;
+import java.lang.reflect.Method;
+import java.util.Enumeration;
+import java.util.Properties;
+
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * This class is based on the log4j class org.apache.log4j.config.PropertySetter that was made by
+ * Anders Kristensen
+ * <p>
+ * General purpose Object property setter. Clients repeatedly invokes {@link #setProperty
+ * setProperty(name,value)} in order to invoke setters on the Object specified in the constructor.
+ * This class relies on the JavaBeans {@link Introspector}to analyze the given Object Class using
+ * reflection.
+ * <p>
+ * Usage:
+ *
+ * <pre>
+ * PropertySetter ps = new PropertySetter( anObject );
+ * ps.set( &quot;name&quot;, &quot;Joe&quot; );
+ * ps.set( &quot;age&quot;, &quot;32&quot; );
+ * ps.set( &quot;isMale&quot;, &quot;true&quot; );
+ * </pre>
+ *
+ * will cause the invocations anObject.setName("Joe"), anObject.setAge(32), and setMale(true) if
+ * such methods exist with those signatures. Otherwise an {@link IntrospectionException}are thrown.
+ */
+public class PropertySetter
+{
+    /** Logger */
+    private static final Log log = LogManager.getLog( PropertySetter.class );
+
+    /** Description of the Field */
+    private final Object obj;
+
+    /** Description of the Field */
+    private PropertyDescriptor[] props;
+
+    /**
+     * Create a new PropertySetter for the specified Object. This is done in preparation for invoking
+     * {@link #setProperty}one or more times.
+     * @param obj the object for which to set properties
+     */
+    public PropertySetter( Object obj )
+    {
+        this.obj = obj;
+    }
+
+    /**
+     * Uses JavaBeans {@link Introspector}to compute setters of object to be configured.
+     */
+    protected void introspect()
+    {
+        try
+        {
+            BeanInfo bi = Introspector.getBeanInfo( obj.getClass() );
+            props = bi.getPropertyDescriptors();
+        }
+        catch ( IntrospectionException ex )
+        {
+            log.error( "Failed to introspect {0}", obj, ex );
+            props = new PropertyDescriptor[0];
+        }
+    }
+
+    /**
+     * Set the properties of an object passed as a parameter in one go. The <code>properties</code>
+     * are parsed relative to a <code>prefix</code>.
+     * <p>
+     * @param obj The object to configure.
+     * @param properties A java.util.Properties containing keys and values.
+     * @param prefix Only keys having the specified prefix will be set.
+     */
+    public static void setProperties( Object obj, Properties properties, String prefix )
+    {
+        new PropertySetter( obj ).setProperties( properties, prefix );
+    }
+
+    /**
+     * Set the properties for the object that match the <code>prefix</code> passed as parameter.
+     * <p>
+     * @param properties The new properties value
+     * @param prefix The new properties value
+     */
+    public void setProperties( Properties properties, String prefix )
+    {
+        int len = prefix.length();
+
+        for ( Enumeration<?> e = properties.propertyNames(); e.hasMoreElements(); )
+        {
+            String key = (String) e.nextElement();
+
+            // handle only properties that start with the desired prefix.
+            if ( key.startsWith( prefix ) )
+            {
+
+                // ignore key if it contains dots after the prefix
+                if ( key.indexOf( '.', len + 1 ) > 0 )
+                {
+                    //System.err.println("----------Ignoring---["+key
+                    //	     +"], prefix=["+prefix+"].");
+                    continue;
+                }
+
+                String value = OptionConverter.findAndSubst( key, properties );
+                key = key.substring( len );
+
+                setProperty( key, value );
+            }
+        }
+
+    }
+
+    /**
+     * Set a property on this PropertySetter's Object. If successful, this method will invoke a
+     * setter method on the underlying Object. The setter is the one for the specified property name
+     * and the value is determined partly from the setter argument type and partly from the value
+     * specified in the call to this method.
+     * <p>
+     * If the setter expects a String no conversion is necessary. If it expects an int, then an
+     * attempt is made to convert 'value' to an int using Integer.valueOf(value). If the setter expects
+     * a boolean, the conversion is by Boolean.valueOf(value).
+     * @param name name of the property
+     * @param value String value of the property
+     */
+
+    public void setProperty( String name, String value )
+    {
+        if ( value == null )
+        {
+            return;
+        }
+
+        name = Introspector.decapitalize( name );
+        PropertyDescriptor prop = getPropertyDescriptor( name );
+
+        //log.debug("---------Key: "+name+", type="+prop.getPropertyType());
+
+        if ( prop == null )
+        {
+            log.warn( "No such property [{0}] in {1}.", name, obj.getClass().getName() );
+        }
+        else
+        {
+            try
+            {
+                setProperty( prop, name, value );
+            }
+            catch ( PropertySetterException ex )
+            {
+                log.warn( "Failed to set property {0} to value \"{1}\".", name, value, ex );
+            }
+        }
+    }
+
+    /**
+     * Set the named property given a {@link PropertyDescriptor}.
+     * @param prop A PropertyDescriptor describing the characteristics of the property to set.
+     * @param name The named of the property to set.
+     * @param value The value of the property.
+     * @throws PropertySetterException
+     */
+
+    public void setProperty( PropertyDescriptor prop, String name, String value )
+        throws PropertySetterException
+    {
+        Method setter = prop.getWriteMethod();
+        if ( setter == null )
+        {
+            throw new PropertySetterException( "No setter for property" );
+        }
+        Class<?>[] paramTypes = setter.getParameterTypes();
+        if ( paramTypes.length != 1 )
+        {
+            throw new PropertySetterException( "#params for setter != 1" );
+        }
+
+        Object arg;
+        try
+        {
+            arg = convertArg( value, paramTypes[0] );
+        }
+        catch ( Throwable t )
+        {
+            throw new PropertySetterException( "Conversion to type [" + paramTypes[0] + "] failed. Reason: " + t );
+        }
+        if ( arg == null )
+        {
+            throw new PropertySetterException( "Conversion to type [" + paramTypes[0] + "] failed." );
+        }
+        log.debug( "Setting property [{0}] to [{1}].", name, arg );
+        try
+        {
+            setter.invoke( obj, new Object[] { arg } );
+        }
+        catch ( Exception ex )
+        {
+            throw new PropertySetterException( ex );
+        }
+    }
+
+    /**
+     * Convert <code>val</code> a String parameter to an object of a given type.
+     * @param val
+     * @param type
+     * @return Object
+     */
+    protected Object convertArg( String val, Class<?> type )
+    {
+        if ( val == null )
+        {
+            return null;
+        }
+
+        String v = val.trim();
+        if ( String.class.isAssignableFrom( type ) )
+        {
+            return val;
+        }
+        else if ( Integer.TYPE.isAssignableFrom( type ) )
+        {
+            return Integer.valueOf( v );
+        }
+        else if ( Long.TYPE.isAssignableFrom( type ) )
+        {
+            return Long.valueOf( v );
+        }
+        else if ( Boolean.TYPE.isAssignableFrom( type ) )
+        {
+            if ( "true".equalsIgnoreCase( v ) )
+            {
+                return Boolean.TRUE;
+            }
+            else if ( "false".equalsIgnoreCase( v ) )
+            {
+                return Boolean.FALSE;
+            }
+        }
+        else if( type.isEnum() )
+        {
+            Enum<?> en = Enum.valueOf(type.asSubclass(Enum.class), v );
+            return en;
+        }
+        else if ( File.class.isAssignableFrom( type ) )
+        {
+            return new File( v );
+        }
+        return null;
+    }
+
+    /**
+     * Gets the propertyDescriptor attribute of the PropertySetter object
+     * @param name
+     * @return The propertyDescriptor value
+     */
+    protected PropertyDescriptor getPropertyDescriptor( String name )
+    {
+        if ( props == null )
+        {
+            introspect();
+        }
+
+        for ( int i = 0; i < props.length; i++ )
+        {
+            if ( name.equals( props[i].getName() ) )
+            {
+                return props[i];
+            }
+        }
+        return null;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/config/PropertySetterException.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/config/PropertySetterException.java
new file mode 100644
index 0000000..633f20c
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/config/PropertySetterException.java
@@ -0,0 +1,75 @@
+package org.apache.commons.jcs3.utils.config;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * This class is based on the log4j class org.apache.log4j.config.PropertySetter that was made by
+ * Anders Kristensen
+ * <p>
+ * Thrown when an error is encountered whilst attempting to set a property using the
+ * {@link PropertySetter}utility class.
+ */
+public class PropertySetterException
+    extends Exception
+{
+    /** DOn't change */
+    private static final long serialVersionUID = -210271658004609028L;
+
+    /** Description of the Field */
+    private final Throwable rootCause;
+
+    /**
+     * Constructor for the PropertySetterException object
+     * <p>
+     * @param msg
+     */
+    public PropertySetterException( String msg )
+    {
+        super( msg );
+        this.rootCause = null;
+    }
+
+    /**
+     * Constructor for the PropertySetterException object
+     * <p>
+     * @param rootCause
+     */
+    public PropertySetterException( Throwable rootCause )
+    {
+        super();
+        this.rootCause = rootCause;
+    }
+
+    /**
+     * Returns descriptive text on the cause of this exception.
+     * <p>
+     * @return The message value
+     */
+    @Override
+    public String getMessage()
+    {
+        String msg = super.getMessage();
+        if ( msg == null && rootCause != null )
+        {
+            msg = rootCause.getMessage();
+        }
+        return msg;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/config/package.html b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/config/package.html
similarity index 100%
rename from commons-jcs-core/src/main/java/org/apache/commons/jcs/utils/config/package.html
rename to commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/config/package.html
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/DiscoveredService.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/DiscoveredService.java
new file mode 100644
index 0000000..d2f252c
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/DiscoveredService.java
@@ -0,0 +1,183 @@
+package org.apache.commons.jcs3.utils.discovery;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.Serializable;
+import java.util.ArrayList;
+
+/**
+ * This contains info about a discovered service. These objects are stored in a set in the
+ * UDPDiscoveryService.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class DiscoveredService
+    implements Serializable
+{
+    /** For serialization. Don't change. */
+    private static final long serialVersionUID = -7810164772089509751L;
+
+    /** region names */
+    private ArrayList<String> cacheNames;
+
+    /** service address */
+    private String serviceAddress;
+
+    /** service port */
+    private int servicePort;
+
+    /** last time we heard from this service? */
+    private long lastHearFromTime = 0;
+
+    /**
+     * @param cacheNames the cacheNames to set
+     */
+    public void setCacheNames( ArrayList<String> cacheNames )
+    {
+        this.cacheNames = cacheNames;
+    }
+
+    /**
+     * @return the cacheNames
+     */
+    public ArrayList<String> getCacheNames()
+    {
+        return cacheNames;
+    }
+
+    /**
+     * @param serviceAddress The serviceAddress to set.
+     */
+    public void setServiceAddress( String serviceAddress )
+    {
+        this.serviceAddress = serviceAddress;
+    }
+
+    /**
+     * @return Returns the serviceAddress.
+     */
+    public String getServiceAddress()
+    {
+        return serviceAddress;
+    }
+
+    /**
+     * @param servicePort The servicePort to set.
+     */
+    public void setServicePort( int servicePort )
+    {
+        this.servicePort = servicePort;
+    }
+
+    /**
+     * @return Returns the servicePort.
+     */
+    public int getServicePort()
+    {
+        return servicePort;
+    }
+
+    /**
+     * @param lastHearFromTime The lastHearFromTime to set.
+     */
+    public void setLastHearFromTime( long lastHearFromTime )
+    {
+        this.lastHearFromTime = lastHearFromTime;
+    }
+
+    /**
+     * @return Returns the lastHearFromTime.
+     */
+    public long getLastHearFromTime()
+    {
+        return lastHearFromTime;
+    }
+
+    /** @return hashcode based on address/port */
+	@Override
+	public int hashCode()
+	{
+		final int prime = 31;
+		int result = 1;
+		result = prime * result
+				+ ((serviceAddress == null) ? 0 : serviceAddress.hashCode());
+		result = prime * result + servicePort;
+		return result;
+	}
+
+	/**
+     * NOTE - this object is often put into sets, so equals needs to be overridden.
+     * <p>
+     * We can't use cache names as part of the equals unless we manually only use the address and
+     * port in a contains check. So that we can use normal set functionality, I've kept the cache
+     * names out.
+     * <p>
+     * @param otherArg other
+     * @return equality based on the address/port
+     */
+	@Override
+	public boolean equals(Object otherArg)
+	{
+		if (this == otherArg)
+		{
+			return true;
+		}
+		if (otherArg == null)
+		{
+			return false;
+		}
+		if (!(otherArg instanceof DiscoveredService))
+		{
+			return false;
+		}
+		DiscoveredService other = (DiscoveredService) otherArg;
+		if (serviceAddress == null)
+		{
+			if (other.serviceAddress != null)
+			{
+				return false;
+			}
+		} else if (!serviceAddress.equals(other.serviceAddress))
+		{
+			return false;
+		}
+		if (servicePort != other.servicePort)
+		{
+			return false;
+		}
+
+		return true;
+	}
+
+    /**
+     * @return string for debugging purposes.
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        buf.append( "\n DiscoveredService" );
+        buf.append( "\n CacheNames = [" + getCacheNames() + "]" );
+        buf.append( "\n ServiceAddress = [" + getServiceAddress() + "]" );
+        buf.append( "\n ServicePort = [" + getServicePort() + "]" );
+        buf.append( "\n LastHearFromTime = [" + getLastHearFromTime() + "]" );
+        return buf.toString();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/UDPCleanupRunner.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/UDPCleanupRunner.java
new file mode 100644
index 0000000..f07b822
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/UDPCleanupRunner.java
@@ -0,0 +1,92 @@
+package org.apache.commons.jcs3.utils.discovery;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * This class periodically check the lastHeardFrom time on the services.
+ * <p>
+ * If they exceed the configurable limit, it removes them from the set.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class UDPCleanupRunner
+    implements Runnable
+{
+    /** log instance */
+    private static final Log log = LogManager.getLog( UDPCleanupRunner.class );
+
+    /** UDP discovery service */
+    private final UDPDiscoveryService discoveryService;
+
+    /** default for max idle time, in seconds */
+    private static final long DEFAULT_MAX_IDLE_TIME_SECONDS = 180;
+
+    /** The configured max idle time, in seconds */
+    private final long maxIdleTimeSeconds = DEFAULT_MAX_IDLE_TIME_SECONDS;
+
+    /**
+     * @param service UDPDiscoveryService
+     */
+    public UDPCleanupRunner( UDPDiscoveryService service )
+    {
+        this.discoveryService = service;
+    }
+
+    /**
+     * This goes through the list of services and removes those that we haven't heard from in longer
+     * than the max idle time.
+     * <p>
+     * @see java.lang.Runnable#run()
+     */
+    @Override
+    public void run()
+    {
+        long now = System.currentTimeMillis();
+
+        // iterate through the set
+        // it is thread safe
+        // TODO this should get a copy.  you can't simply remove from this.
+        // the listeners need to be notified.
+        Set<DiscoveredService> toRemove = new HashSet<>();
+        // can't remove via the iterator. must remove directly
+        for (DiscoveredService service : discoveryService.getDiscoveredServices())
+        {
+            if ( ( now - service.getLastHearFromTime() ) > ( maxIdleTimeSeconds * 1000 ) )
+            {
+                log.info( "Removing service, since we haven't heard from it in "
+                        + "{0} seconds. service = {1}", maxIdleTimeSeconds, service );
+                toRemove.add( service );
+            }
+        }
+
+        // remove the bad ones
+        for (DiscoveredService service : toRemove)
+        {
+            // call this so the listeners get notified
+            discoveryService.removeDiscoveredService( service );
+        }
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoveryAttributes.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoveryAttributes.java
new file mode 100644
index 0000000..d0d8c04
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoveryAttributes.java
@@ -0,0 +1,269 @@
+package org.apache.commons.jcs3.utils.discovery;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Configuration properties for UDP discover service.
+ * <p>
+ * The service will allow out applications to find each other.
+ * <p>
+ * @author Aaron Smuts
+ */
+public final class UDPDiscoveryAttributes
+    implements Cloneable
+{
+    /** service name */
+    private String serviceName;
+
+    /** service address */
+    private String serviceAddress;
+
+    /** service port */
+    private int servicePort;
+
+    /**
+     * false -> this service instance is not ready to receive requests. true -> ready for use
+     */
+    private boolean isDark;
+
+    /** default udp discovery address */
+    private static final String DEFAULT_UDP_DISCOVERY_ADDRESS = "228.4.5.6";
+
+    /** default udp discovery port */
+    private static final int DEFAULT_UDP_DISCOVERY_PORT = 5678;
+
+    /** udp discovery address */
+    private String udpDiscoveryAddr = DEFAULT_UDP_DISCOVERY_ADDRESS;
+
+    /** udp discovery network interface */
+    private String udpDiscoveryInterface = null;
+
+    /** udp discovery port */
+    private int udpDiscoveryPort = DEFAULT_UDP_DISCOVERY_PORT;
+
+    /** udp datagram TTL */
+    private int udpTTL = 0;
+
+    /** default delay between sending passive broadcasts */
+    private static final int DEFAULT_SEND_DELAY_SEC = 60;
+
+    /** delay between sending passive broadcasts */
+    private int sendDelaySec = DEFAULT_SEND_DELAY_SEC;
+
+    /** default amount of time before we remove services that we haven't heard from */
+    private static final int DEFAULT_MAX_IDLE_TIME_SEC = 180;
+
+    /** amount of time before we remove services that we haven't heard from */
+    private int maxIdleTimeSec = DEFAULT_MAX_IDLE_TIME_SEC;
+
+    /**
+     * @param serviceName The serviceName to set.
+     */
+    public void setServiceName( String serviceName )
+    {
+        this.serviceName = serviceName;
+    }
+
+    /**
+     * @return Returns the serviceName.
+     */
+    public String getServiceName()
+    {
+        return serviceName;
+    }
+
+    /**
+     * @param serviceAddress The serviceAddress to set.
+     */
+    public void setServiceAddress( String serviceAddress )
+    {
+        this.serviceAddress = serviceAddress;
+    }
+
+    /**
+     * @return Returns the serviceAddress.
+     */
+    public String getServiceAddress()
+    {
+        return serviceAddress;
+    }
+
+    /**
+     * @param servicePort The servicePort to set.
+     */
+    public void setServicePort( int servicePort )
+    {
+        this.servicePort = servicePort;
+    }
+
+    /**
+     * @return Returns the servicePort.
+     */
+    public int getServicePort()
+    {
+        return servicePort;
+    }
+
+    /**
+     * @param udpDiscoveryAddr The udpDiscoveryAddr to set.
+     */
+    public void setUdpDiscoveryAddr( String udpDiscoveryAddr )
+    {
+        this.udpDiscoveryAddr = udpDiscoveryAddr;
+    }
+
+    /**
+     * @return Returns the udpDiscoveryAddr.
+     */
+    public String getUdpDiscoveryAddr()
+    {
+        return udpDiscoveryAddr;
+    }
+
+    /**
+     * @param udpDiscoveryInterface The udpDiscoveryInterface to set.
+     */
+    public void setUdpDiscoveryInterface( String udpDiscoveryInterface )
+    {
+        this.udpDiscoveryInterface = udpDiscoveryInterface;
+    }
+
+    /**
+     * @return Returns the udpDiscoveryInterface.
+     */
+    public String getUdpDiscoveryInterface()
+    {
+        return udpDiscoveryInterface;
+    }
+
+    /**
+     * @param udpDiscoveryPort The udpDiscoveryPort to set.
+     */
+    public void setUdpDiscoveryPort( int udpDiscoveryPort )
+    {
+        this.udpDiscoveryPort = udpDiscoveryPort;
+    }
+
+    /**
+     * @return Returns the udpTTL.
+     */
+    public int getUdpTTL()
+    {
+        return udpTTL;
+    }
+
+    /**
+     * @param udpTTL The udpTTL to set.
+     */
+    public void setUdpTTL( int udpTTL )
+    {
+        this.udpTTL = udpTTL;
+    }
+
+    /**
+     * @return Returns the udpDiscoveryPort.
+     */
+    public int getUdpDiscoveryPort()
+    {
+        return udpDiscoveryPort;
+    }
+
+    /**
+     * @param sendDelaySec The sendDelaySec to set.
+     */
+    public void setSendDelaySec( int sendDelaySec )
+    {
+        this.sendDelaySec = sendDelaySec;
+    }
+
+    /**
+     * @return Returns the sendDelaySec.
+     */
+    public int getSendDelaySec()
+    {
+        return sendDelaySec;
+    }
+
+    /**
+     * @param maxIdleTimeSec The maxIdleTimeSec to set.
+     */
+    public void setMaxIdleTimeSec( int maxIdleTimeSec )
+    {
+        this.maxIdleTimeSec = maxIdleTimeSec;
+    }
+
+    /**
+     * @return Returns the maxIdleTimeSec.
+     */
+    public int getMaxIdleTimeSec()
+    {
+        return maxIdleTimeSec;
+    }
+
+    /**
+     * @return Returns the isDark.
+     */
+    public boolean isDark()
+    {
+        return isDark;
+    }
+
+    /**
+     * @param isDark The isDark to set.
+     */
+    public void setDark( boolean isDark )
+    {
+        this.isDark = isDark;
+    }
+
+    /** @return a clone of this object */
+    @Override
+    public UDPDiscoveryAttributes clone()
+    {
+        UDPDiscoveryAttributes attributes = new UDPDiscoveryAttributes();
+        attributes.setSendDelaySec( this.getSendDelaySec() );
+        attributes.setMaxIdleTimeSec( this.getMaxIdleTimeSec() );
+        attributes.setServiceName( this.getServiceName() );
+        attributes.setServicePort( this.getServicePort() );
+        attributes.setUdpDiscoveryAddr( this.getUdpDiscoveryAddr() );
+        attributes.setUdpDiscoveryPort( this.getUdpDiscoveryPort() );
+        attributes.setDark( this.isDark() );
+        return attributes;
+    }
+
+    /**
+     * @return string for debugging purposes.
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        buf.append( "\n UDPDiscoveryAttributes" );
+        buf.append( "\n ServiceName = [" + getServiceName() + "]" );
+        buf.append( "\n ServiceAddress = [" + getServiceAddress() + "]" );
+        buf.append( "\n ServicePort = [" + getServicePort() + "]" );
+        buf.append( "\n UdpDiscoveryAddr = [" + getUdpDiscoveryAddr() + "]" );
+        buf.append( "\n UdpDiscoveryPort = [" + getUdpDiscoveryPort() + "]" );
+        buf.append( "\n SendDelaySec = [" + getSendDelaySec() + "]" );
+        buf.append( "\n MaxIdleTimeSec = [" + getMaxIdleTimeSec() + "]" );
+        buf.append( "\n IsDark = [" + isDark() + "]" );
+        return buf.toString();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoveryManager.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoveryManager.java
new file mode 100644
index 0000000..40f7a44
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoveryManager.java
@@ -0,0 +1,108 @@
+package org.apache.commons.jcs3.utils.discovery;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
+import org.apache.commons.jcs3.engine.behavior.IProvideScheduler;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * This manages UDPDiscovery Services. We should end up with one service per Lateral Cache Manager
+ * Instance. One service works for multiple regions. We don't want a connection for each region.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class UDPDiscoveryManager
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog( UDPDiscoveryManager.class );
+
+    /** Singleton instance */
+    private static UDPDiscoveryManager INSTANCE = new UDPDiscoveryManager();
+
+    /** Known services */
+    private final ConcurrentMap<String, UDPDiscoveryService> services = new ConcurrentHashMap<>();
+
+    /** private for singleton */
+    private UDPDiscoveryManager()
+    {
+        // noopt
+    }
+
+    /**
+     * Singleton
+     * <p>
+     * @return UDPDiscoveryManager
+     */
+    public static UDPDiscoveryManager getInstance()
+    {
+        return INSTANCE;
+    }
+
+    /**
+     * Creates a service for the address and port if one doesn't exist already.
+     * <p>
+     * We need to key this using the listener port too. TODO think of making one discovery service
+     * work for multiple types of clients.
+     * <p>
+     * @param discoveryAddress
+     * @param discoveryPort
+     * @param servicePort
+     * @param cacheMgr
+     * @return UDPDiscoveryService
+     */
+    public UDPDiscoveryService getService( String discoveryAddress, int discoveryPort, int servicePort,
+                                                        ICompositeCacheManager cacheMgr )
+    {
+        String key = discoveryAddress + ":" + discoveryPort + ":" + servicePort;
+
+        UDPDiscoveryService service = services.computeIfAbsent(key, k -> {
+            log.info( "Creating service for address:port:servicePort [{0}]", key );
+
+            UDPDiscoveryAttributes attributes = new UDPDiscoveryAttributes();
+            attributes.setUdpDiscoveryAddr( discoveryAddress );
+            attributes.setUdpDiscoveryPort( discoveryPort );
+            attributes.setServicePort( servicePort );
+
+            UDPDiscoveryService newService = new UDPDiscoveryService( attributes );
+
+            // register for shutdown notification
+            cacheMgr.registerShutdownObserver( newService );
+
+            // inject scheduler
+            if ( cacheMgr instanceof IProvideScheduler)
+            {
+                newService.setScheduledExecutorService(((IProvideScheduler)cacheMgr)
+                        .getScheduledExecutorService());
+            }
+
+            newService.startup();
+            return newService;
+        });
+
+        log.debug( "Returning service [{0}] for key [{1}]", service, key );
+
+        return service;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoveryMessage.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoveryMessage.java
new file mode 100644
index 0000000..70eeb1e
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoveryMessage.java
@@ -0,0 +1,166 @@
+package org.apache.commons.jcs3.utils.discovery;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.Serializable;
+import java.util.ArrayList;
+
+/**
+ * The message sent by the discovery mechanism.
+ */
+public class UDPDiscoveryMessage
+    implements Serializable
+{
+    /** Don't change */
+    private static final long serialVersionUID = -5332377899560951793L;
+
+    public enum BroadcastType
+    {
+        /**
+         * This is the periodic broadcast of a servers location. This type of message is also sent in
+         * response to a REQUEST_BROADCAST.
+         */
+        PASSIVE,
+
+        /**
+         * This asks recipients to broadcast their location. This is used on startup.
+         */
+        REQUEST,
+
+        /**
+         * This message instructs the receiver to remove this service from its list.
+         */
+        REMOVE
+    }
+
+    /** The message type */
+    private BroadcastType messageType = BroadcastType.PASSIVE;
+
+    /** udp port */
+    private int port = 6789;
+
+    /** UDP host */
+    private String host = "228.5.6.7";
+
+    /** Id of the requester, allows self-filtration */
+    private long requesterId;
+
+    /** Names of regions */
+    private ArrayList<String> cacheNames = new ArrayList<>();
+
+    /**
+     * @param port The port to set.
+     */
+    public void setPort( int port )
+    {
+        this.port = port;
+    }
+
+    /**
+     * @return Returns the port.
+     */
+    public int getPort()
+    {
+        return port;
+    }
+
+    /**
+     * @param host The host to set.
+     */
+    public void setHost( String host )
+    {
+        this.host = host;
+    }
+
+    /**
+     * @return Returns the host.
+     */
+    public String getHost()
+    {
+        return host;
+    }
+
+    /**
+     * @param requesterId The requesterId to set.
+     */
+    public void setRequesterId( long requesterId )
+    {
+        this.requesterId = requesterId;
+    }
+
+    /**
+     * @return Returns the requesterId.
+     */
+    public long getRequesterId()
+    {
+        return requesterId;
+    }
+
+    /**
+     * @param messageType The messageType to set.
+     */
+    public void setMessageType( BroadcastType messageType )
+    {
+        this.messageType = messageType;
+    }
+
+    /**
+     * @return Returns the messageType.
+     */
+    public BroadcastType getMessageType()
+    {
+        return messageType;
+    }
+
+    /**
+     * @param cacheNames The cacheNames to set.
+     */
+    public void setCacheNames( ArrayList<String> cacheNames )
+    {
+        this.cacheNames = cacheNames;
+    }
+
+    /**
+     * @return Returns the cacheNames.
+     */
+    public ArrayList<String> getCacheNames()
+    {
+        return cacheNames;
+    }
+
+    /**
+     * @return debugging string
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        buf.append( "\n host = [" + host + "]" );
+        buf.append( "\n port = [" + port + "]" );
+        buf.append( "\n requesterId = [" + requesterId + "]" );
+        buf.append( "\n messageType = [" + messageType + "]" );
+        buf.append( "\n Cache Names" );
+        for (String name : cacheNames)
+        {
+            buf.append( " cacheName = [" + name + "]" );
+        }
+        return buf.toString();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoveryReceiver.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoveryReceiver.java
new file mode 100644
index 0000000..361b90a
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoveryReceiver.java
@@ -0,0 +1,366 @@
+package org.apache.commons.jcs3.utils.discovery;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.net.DatagramPacket;
+import java.net.InetAddress;
+import java.net.MulticastSocket;
+import java.net.NetworkInterface;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.commons.jcs3.engine.CacheInfo;
+import org.apache.commons.jcs3.engine.behavior.IShutdownObserver;
+import org.apache.commons.jcs3.io.ObjectInputStreamClassLoaderAware;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.discovery.UDPDiscoveryMessage.BroadcastType;
+import org.apache.commons.jcs3.utils.net.HostNameUtil;
+import org.apache.commons.jcs3.utils.threadpool.PoolConfiguration;
+import org.apache.commons.jcs3.utils.threadpool.ThreadPoolManager;
+import org.apache.commons.jcs3.utils.threadpool.PoolConfiguration.WhenBlockedPolicy;
+
+/** Receives UDP Discovery messages. */
+public class UDPDiscoveryReceiver
+    implements Runnable, IShutdownObserver
+{
+    /** The log factory */
+    private static final Log log = LogManager.getLog( UDPDiscoveryReceiver.class );
+
+    /** buffer */
+    private final byte[] mBuffer = new byte[65536];
+
+    /** The socket used for communication. */
+    private MulticastSocket mSocket;
+
+    /**
+     * TODO: Consider using the threadpool manager to get this thread pool. For now place a tight
+     * restriction on the pool size
+     */
+    private static final int maxPoolSize = 2;
+
+    /** The processor */
+    private final ExecutorService pooledExecutor;
+
+    /** number of messages received. For debugging and testing. */
+    private final AtomicInteger cnt = new AtomicInteger(0);
+
+    /** Service to get cache names and handle request broadcasts */
+    private final UDPDiscoveryService service;
+
+    /** Multicast address */
+    private final InetAddress multicastAddress;
+
+    /** Is it shutdown. */
+    private boolean shutdown = false;
+
+    /**
+     * Constructor for the LateralUDPReceiver object.
+     * <p>
+     * We determine out own host using InetAddress
+     *<p>
+     * @param service
+     * @param multicastInterfaceString
+     * @param multicastAddressString
+     * @param multicastPort
+     * @throws IOException
+     */
+    public UDPDiscoveryReceiver( UDPDiscoveryService service, String multicastInterfaceString,
+            String multicastAddressString, int multicastPort )
+        throws IOException
+    {
+        this.service = service;
+        this.multicastAddress = InetAddress.getByName( multicastAddressString );
+
+        // create a small thread pool to handle a barrage
+        this.pooledExecutor = ThreadPoolManager.getInstance().createPool(
+        		new PoolConfiguration(false, 0, maxPoolSize, maxPoolSize, 0,
+        		        WhenBlockedPolicy.DISCARDOLDEST, maxPoolSize),
+        		"JCS-UDPDiscoveryReceiver-", Thread.MIN_PRIORITY);
+
+        log.info( "Constructing listener, [{0}:{1}]", multicastAddress, multicastPort );
+
+        createSocket( multicastInterfaceString, multicastAddress, multicastPort );
+    }
+
+    /**
+     * Creates the socket for this class.
+     * <p>
+     * @param multicastInterfaceString
+     * @param multicastAddress
+     * @param multicastPort
+     * @throws IOException
+     */
+    private void createSocket( String multicastInterfaceString, InetAddress multicastAddress,
+            int multicastPort )
+        throws IOException
+    {
+        try
+        {
+            mSocket = new MulticastSocket( multicastPort );
+            if (log.isInfoEnabled())
+            {
+                log.info( "Joining Group: [{0}]", multicastAddress );
+            }
+
+            // Use dedicated interface if specified
+            NetworkInterface multicastInterface = null;
+            if (multicastInterfaceString != null)
+            {
+                multicastInterface = NetworkInterface.getByName(multicastInterfaceString);
+            }
+            else
+            {
+                multicastInterface = HostNameUtil.getMulticastNetworkInterface();
+            }
+            if (multicastInterface != null)
+            {
+                log.info("Using network interface {0}", multicastInterface.getDisplayName());
+                mSocket.setNetworkInterface(multicastInterface);
+            }
+
+            mSocket.joinGroup( multicastAddress );
+        }
+        catch ( IOException e )
+        {
+            log.error( "Could not bind to multicast address [{0}:{1}]", multicastAddress,
+                    multicastPort, e );
+            throw e;
+        }
+    }
+
+    /**
+     * Highly unreliable. If it is processing one message while another comes in, the second
+     * message is lost. This is for low concurrency peppering.
+     * <p>
+     * @return the object message
+     * @throws IOException
+     */
+    public Object waitForMessage()
+        throws IOException
+    {
+        final DatagramPacket packet = new DatagramPacket( mBuffer, mBuffer.length );
+        Object obj = null;
+        try
+        {
+            log.debug( "Waiting for message." );
+
+            mSocket.receive( packet );
+
+            log.debug( "Received packet from address [{0}]",
+                    () -> packet.getSocketAddress() );
+
+            try (ByteArrayInputStream byteStream = new ByteArrayInputStream(mBuffer, 0, packet.getLength());
+                 ObjectInputStream objectStream = new ObjectInputStreamClassLoaderAware(byteStream, null))
+            {
+                obj = objectStream.readObject();
+            }
+
+            if ( obj instanceof UDPDiscoveryMessage )
+            {
+            	// Ensure that the address we're supposed to send to is, indeed, the address
+            	// of the machine on the other end of this connection.  This guards against
+            	// instances where we don't exactly get the right local host address
+            	UDPDiscoveryMessage msg = (UDPDiscoveryMessage) obj;
+            	msg.setHost(packet.getAddress().getHostAddress());
+
+                log.debug( "Read object from address [{0}], object=[{1}]",
+                        packet.getSocketAddress(), obj );
+            }
+        }
+        catch ( Exception e )
+        {
+            log.error( "Error receiving multicast packet", e );
+        }
+
+        return obj;
+    }
+
+    /** Main processing method for the LateralUDPReceiver object */
+    @Override
+    public void run()
+    {
+        try
+        {
+            while ( !shutdown )
+            {
+                Object obj = waitForMessage();
+
+                cnt.incrementAndGet();
+
+                log.debug( "{0} messages received.", () -> getCnt() );
+
+                UDPDiscoveryMessage message = null;
+
+                try
+                {
+                    message = (UDPDiscoveryMessage) obj;
+                    // check for null
+                    if ( message != null )
+                    {
+                        MessageHandler handler = new MessageHandler( message );
+
+                        pooledExecutor.execute( handler );
+
+                        log.debug( "Passed handler to executor." );
+                    }
+                    else
+                    {
+                        log.warn( "message is null" );
+                    }
+                }
+                catch ( ClassCastException cce )
+                {
+                    log.warn( "Received unknown message type", cce.getMessage() );
+                }
+            } // end while
+        }
+        catch ( IOException e )
+        {
+            log.error( "Unexpected exception in UDP receiver.", e );
+            try
+            {
+                Thread.sleep( 100 );
+                // TODO consider some failure count so we don't do this
+                // forever.
+            }
+            catch ( InterruptedException e2 )
+            {
+                log.error( "Problem sleeping", e2 );
+            }
+        }
+    }
+
+    /**
+     * @param cnt The cnt to set.
+     */
+    public void setCnt( int cnt )
+    {
+        this.cnt.set(cnt);
+    }
+
+    /**
+     * @return Returns the cnt.
+     */
+    public int getCnt()
+    {
+        return cnt.get();
+    }
+
+    /**
+     * Separate thread run when a command comes into the UDPDiscoveryReceiver.
+     */
+    public class MessageHandler
+        implements Runnable
+    {
+        /** The message to handle. Passed in during construction. */
+        private UDPDiscoveryMessage message = null;
+
+        /**
+         * @param message
+         */
+        public MessageHandler( UDPDiscoveryMessage message )
+        {
+            this.message = message;
+        }
+
+        /**
+         * Process the message.
+         */
+        @SuppressWarnings("synthetic-access")
+        @Override
+        public void run()
+        {
+            // consider comparing ports here instead.
+            if ( message.getRequesterId() == CacheInfo.listenerId )
+            {
+                log.debug( "Ignoring message sent from self" );
+            }
+            else
+            {
+                log.debug( "Process message sent from another" );
+                log.debug( "Message = {0}", message );
+
+                if ( message.getHost() == null || message.getCacheNames() == null || message.getCacheNames().isEmpty() )
+                {
+                    log.debug( "Ignoring invalid message: {0}", message );
+                }
+                else
+                {
+                    processMessage();
+                }
+            }
+        }
+
+        /**
+         * Process the incoming message.
+         */
+        @SuppressWarnings("synthetic-access")
+        private void processMessage()
+        {
+            DiscoveredService discoveredService = new DiscoveredService();
+            discoveredService.setServiceAddress( message.getHost() );
+            discoveredService.setCacheNames( message.getCacheNames() );
+            discoveredService.setServicePort( message.getPort() );
+            discoveredService.setLastHearFromTime( System.currentTimeMillis() );
+
+            // if this is a request message, have the service handle it and
+            // return
+            if ( message.getMessageType() == BroadcastType.REQUEST )
+            {
+                log.debug( "Message is a Request Broadcast, will have the service handle it." );
+                service.serviceRequestBroadcast();
+                return;
+            }
+            else if ( message.getMessageType() == BroadcastType.REMOVE )
+            {
+                log.debug( "Removing service from set {0}", discoveredService );
+                service.removeDiscoveredService( discoveredService );
+            }
+            else
+            {
+                service.addOrUpdateService( discoveredService );
+            }
+        }
+    }
+
+    /** Shuts down the socket. */
+    @Override
+    public void shutdown()
+    {
+        if (!shutdown)
+        {
+            try
+            {
+                shutdown = true;
+                mSocket.leaveGroup( multicastAddress );
+                mSocket.close();
+                pooledExecutor.shutdownNow();
+            }
+            catch ( IOException e )
+            {
+                log.error( "Problem closing socket" );
+            }
+        }
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoverySender.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoverySender.java
new file mode 100644
index 0000000..0710bdc
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoverySender.java
@@ -0,0 +1,260 @@
+package org.apache.commons.jcs3.utils.discovery;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.InetAddress;
+import java.net.MulticastSocket;
+import java.util.ArrayList;
+
+import org.apache.commons.jcs3.engine.CacheInfo;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.discovery.UDPDiscoveryMessage.BroadcastType;
+import org.apache.commons.jcs3.utils.serialization.StandardSerializer;
+
+/**
+ * This is a generic sender for the UDPDiscovery process.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class UDPDiscoverySender implements AutoCloseable
+{
+    /** The logger. */
+    private static final Log log = LogManager.getLog( UDPDiscoverySender.class );
+
+    /** The socket */
+    private final MulticastSocket localSocket;
+
+    /** The address */
+    private final InetAddress multicastAddress;
+
+    /** The port */
+    private final int multicastPort;
+
+    /** Used to serialize messages */
+    private final StandardSerializer serializer = new StandardSerializer();
+
+    /**
+     * Constructor for the UDPDiscoverySender object
+     * <p>
+     * This sender can be used to send multiple messages.
+     * <p>
+     * When you are done sending, you should destroy the socket sender.
+     * <p>
+     * @param host
+     * @param port
+     * @param udpTTL the Datagram packet time-to-live
+     * @throws IOException
+     */
+    public UDPDiscoverySender( String host, int port, int udpTTL )
+        throws IOException
+    {
+        try
+        {
+            log.debug( "Constructing socket for sender on port [{0}]", port );
+            localSocket = new MulticastSocket( port );
+            if (udpTTL > 0)
+            {
+                log.debug( "Setting datagram TTL to [{0}]", udpTTL );
+                localSocket.setTimeToLive(udpTTL);
+            }
+
+            // Remote address.
+            multicastAddress = InetAddress.getByName( host );
+        }
+        catch ( IOException e )
+        {
+            log.error( "Could not bind to multicast address [{0}]", host, e );
+            throw e;
+        }
+
+        this.multicastPort = port;
+    }
+
+    /**
+     * Closes the socket connection.
+     */
+    @Override
+    public void close()
+    {
+        if ( this.localSocket != null && !this.localSocket.isClosed() )
+        {
+            this.localSocket.close();
+        }
+    }
+
+    /**
+     * Send messages.
+     * <p>
+     * @param message
+     * @throws IOException
+     */
+    public void send( UDPDiscoveryMessage message )
+        throws IOException
+    {
+        if ( this.localSocket == null )
+        {
+            throw new IOException( "Socket is null, cannot send message." );
+        }
+
+        if ( this.localSocket.isClosed() )
+        {
+            throw new IOException( "Socket is closed, cannot send message." );
+        }
+
+        log.debug( "sending UDPDiscoveryMessage, address [{0}], port [{1}], "
+                + "message = {2}", multicastAddress, multicastPort, message );
+
+        try
+        {
+            final byte[] bytes = serializer.serialize( message );
+
+            // put the byte array in a packet
+            final DatagramPacket packet = new DatagramPacket( bytes, bytes.length, multicastAddress, multicastPort );
+
+            log.debug( "Sending DatagramPacket. bytes.length [{0}] to {1}:{2}",
+                    bytes.length, multicastAddress, multicastPort );
+
+            localSocket.send( packet );
+        }
+        catch ( IOException e )
+        {
+            log.error( "Error sending message", e );
+            throw e;
+        }
+    }
+
+    /**
+     * Ask other to broadcast their info the the multicast address. If a lateral is non receiving it
+     * can use this. This is also called on startup so we can get info.
+     * <p>
+     * @throws IOException
+     */
+    public void requestBroadcast()
+        throws IOException
+    {
+        log.debug( "sending requestBroadcast" );
+
+        UDPDiscoveryMessage message = new UDPDiscoveryMessage();
+        message.setRequesterId( CacheInfo.listenerId );
+        message.setMessageType( BroadcastType.REQUEST );
+        send( message );
+    }
+
+    /**
+     * This sends a message broadcasting out that the host and port is available for connections.
+     * <p>
+     * It uses the vmid as the requesterDI
+     * @param host
+     * @param port
+     * @param cacheNames
+     * @throws IOException
+     */
+    public void passiveBroadcast( String host, int port, ArrayList<String> cacheNames )
+        throws IOException
+    {
+        passiveBroadcast( host, port, cacheNames, CacheInfo.listenerId );
+    }
+
+    /**
+     * This allows you to set the sender id. This is mainly for testing.
+     * <p>
+     * @param host
+     * @param port
+     * @param cacheNames names of the cache regions
+     * @param listenerId
+     * @throws IOException
+     */
+    protected void passiveBroadcast( String host, int port, ArrayList<String> cacheNames, long listenerId )
+        throws IOException
+    {
+        log.debug( "sending passiveBroadcast" );
+
+        UDPDiscoveryMessage message = new UDPDiscoveryMessage();
+        message.setHost( host );
+        message.setPort( port );
+        message.setCacheNames( cacheNames );
+        message.setRequesterId( listenerId );
+        message.setMessageType( BroadcastType.PASSIVE );
+        send( message );
+    }
+
+    /**
+     * This sends a message broadcasting our that the host and port is no longer available.
+     * <p>
+     * It uses the vmid as the requesterID
+     * <p>
+     * @param host host
+     * @param port port
+     * @param cacheNames names of the cache regions
+     * @throws IOException on error
+     */
+    public void removeBroadcast( String host, int port, ArrayList<String> cacheNames )
+        throws IOException
+    {
+        removeBroadcast( host, port, cacheNames, CacheInfo.listenerId );
+    }
+
+    /**
+     * This allows you to set the sender id. This is mainly for testing.
+     * <p>
+     * @param host host
+     * @param port port
+     * @param cacheNames names of the cache regions
+     * @param listenerId listener ID
+     * @throws IOException on error
+     */
+    protected void removeBroadcast( String host, int port, ArrayList<String> cacheNames, long listenerId )
+        throws IOException
+    {
+        log.debug( "sending removeBroadcast" );
+
+        UDPDiscoveryMessage message = new UDPDiscoveryMessage();
+        message.setHost( host );
+        message.setPort( port );
+        message.setCacheNames( cacheNames );
+        message.setRequesterId( listenerId );
+        message.setMessageType( BroadcastType.REMOVE );
+        send( message );
+    }
+}
+
+/**
+ * This allows us to get the byte array from an output stream.
+ * <p>
+ * @author asmuts
+ * @created January 15, 2002
+ */
+
+class MyByteArrayOutputStream
+    extends ByteArrayOutputStream
+{
+    /**
+     * Gets the bytes attribute of the MyByteArrayOutputStream object
+     * @return The bytes value
+     */
+    public byte[] getBytes()
+    {
+        return buf;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoverySenderThread.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoverySenderThread.java
new file mode 100644
index 0000000..21ba9fc
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoverySenderThread.java
@@ -0,0 +1,147 @@
+package org.apache.commons.jcs3.utils.discovery;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * Used to periodically broadcast our location to other caches that might be listening.
+ */
+public class UDPDiscoverySenderThread
+    implements Runnable
+{
+    /** The logger. */
+    private static final Log log = LogManager.getLog( UDPDiscoverySenderThread.class );
+
+    /**
+     * details of the host, port, and service being advertised to listen for TCP socket connections
+     */
+    private final UDPDiscoveryAttributes attributes;
+
+    /** List of known regions. */
+    private ArrayList<String> cacheNames = new ArrayList<>();
+
+    /**
+     * @param cacheNames The cacheNames to set.
+     */
+    protected void setCacheNames( ArrayList<String> cacheNames )
+    {
+        log.info( "Resetting cacheNames = [{0}]", cacheNames );
+        this.cacheNames = cacheNames;
+    }
+
+    /**
+     * @return Returns the cacheNames.
+     */
+    protected ArrayList<String> getCacheNames()
+    {
+        return cacheNames;
+    }
+
+    /**
+     * Constructs the sender with the port to tell others to connect to.
+     * <p>
+     * On construction the sender will request that the other caches let it know their addresses.
+     * @param attributes host, port, etc.
+     * @param cacheNames List of strings of the names of the region participating.
+     */
+    public UDPDiscoverySenderThread( UDPDiscoveryAttributes attributes, ArrayList<String> cacheNames )
+    {
+        this.attributes = attributes;
+
+        this.cacheNames = cacheNames;
+
+        log.debug( "Creating sender thread for discoveryAddress = [{0}] and "
+                + "discoveryPort = [{1}] myHostName = [{2}] and port = [{3}]",
+                () -> attributes.getUdpDiscoveryAddr(),
+                () -> attributes.getUdpDiscoveryPort(),
+                () -> attributes.getServiceAddress(),
+                () -> attributes.getServicePort() );
+
+        try (UDPDiscoverySender sender = new UDPDiscoverySender(
+                attributes.getUdpDiscoveryAddr(),
+                attributes.getUdpDiscoveryPort(),
+                attributes.getUdpTTL()))
+        {
+            // move this to the run method and determine how often to call it.
+            sender.requestBroadcast();
+
+            log.debug( "Sent a request broadcast to the group" );
+        }
+        catch ( IOException e )
+        {
+            log.error( "Problem sending a Request Broadcast", e );
+        }
+    }
+
+    /**
+     * Send a message.
+     */
+    @Override
+    public void run()
+    {
+        // create this connection each time.
+        // more robust
+        try (UDPDiscoverySender sender = new UDPDiscoverySender(
+                attributes.getUdpDiscoveryAddr(),
+                attributes.getUdpDiscoveryPort(),
+                attributes.getUdpTTL()))
+        {
+            sender.passiveBroadcast( attributes.getServiceAddress(), attributes.getServicePort(), cacheNames );
+
+            // todo we should consider sending a request broadcast every so
+            // often.
+
+            log.debug( "Called sender to issue a passive broadcast" );
+        }
+        catch ( IOException e )
+        {
+            log.error( "Problem calling the UDP Discovery Sender [{0}:{1}]",
+                    attributes.getUdpDiscoveryAddr(),
+                    attributes.getUdpDiscoveryPort(), e );
+        }
+    }
+
+    /**
+     * Issues a remove broadcast to the others.
+     */
+    protected void shutdown()
+    {
+        // create this connection each time.
+        // more robust
+        try (UDPDiscoverySender sender = new UDPDiscoverySender(
+                attributes.getUdpDiscoveryAddr(),
+                attributes.getUdpDiscoveryPort(),
+                attributes.getUdpTTL()))
+        {
+            sender.removeBroadcast( attributes.getServiceAddress(), attributes.getServicePort(), cacheNames );
+
+            log.debug( "Called sender to issue a remove broadcast in shudown." );
+        }
+        catch ( IOException e )
+        {
+            log.error( "Problem calling the UDP Discovery Sender", e );
+        }
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoveryService.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoveryService.java
new file mode 100644
index 0000000..534ef1f
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoveryService.java
@@ -0,0 +1,372 @@
+package org.apache.commons.jcs3.utils.discovery;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.jcs3.engine.behavior.IRequireScheduler;
+import org.apache.commons.jcs3.engine.behavior.IShutdownObserver;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.discovery.behavior.IDiscoveryListener;
+import org.apache.commons.jcs3.utils.net.HostNameUtil;
+
+/**
+ * This service creates a listener that can create lateral caches and add them to the no wait list.
+ * <p>
+ * It also creates a sender that periodically broadcasts its availability.
+ * <p>
+ * The sender also broadcasts a request for other caches to broadcast their addresses.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class UDPDiscoveryService
+    implements IShutdownObserver, IRequireScheduler
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog( UDPDiscoveryService.class );
+
+    /** thread that listens for messages */
+    private Thread udpReceiverThread;
+
+    /** the runnable that the receiver thread runs */
+    private UDPDiscoveryReceiver receiver;
+
+    /** the runnable that sends messages via the clock daemon */
+    private UDPDiscoverySenderThread sender = null;
+
+    /** attributes */
+    private UDPDiscoveryAttributes udpDiscoveryAttributes = null;
+
+    /** is this shut down? */
+    private boolean shutdown = false;
+
+    /** This is a set of services that have been discovered. */
+    private final Set<DiscoveredService> discoveredServices = new CopyOnWriteArraySet<>();
+
+    /** This a list of regions that are configured to use discovery. */
+    private final Set<String> cacheNames = new CopyOnWriteArraySet<>();
+
+    /** Set of listeners. */
+    private final Set<IDiscoveryListener> discoveryListeners = new CopyOnWriteArraySet<>();
+
+    /**
+     * @param attributes
+     */
+    public UDPDiscoveryService( UDPDiscoveryAttributes attributes)
+    {
+        udpDiscoveryAttributes = attributes.clone();
+
+        try
+        {
+            // todo, you should be able to set this
+            udpDiscoveryAttributes.setServiceAddress( HostNameUtil.getLocalHostAddress() );
+        }
+        catch ( UnknownHostException e )
+        {
+            log.error( "Couldn't get localhost address", e );
+        }
+
+        try
+        {
+            // todo need some kind of recovery here.
+            receiver = new UDPDiscoveryReceiver( this,
+                    getUdpDiscoveryAttributes().getUdpDiscoveryInterface(),
+                    getUdpDiscoveryAttributes().getUdpDiscoveryAddr(),
+                    getUdpDiscoveryAttributes().getUdpDiscoveryPort() );
+        }
+        catch ( IOException e )
+        {
+            log.error( "Problem creating UDPDiscoveryReceiver, address [{0}] "
+                    + "port [{1}] we won't be able to find any other caches",
+                    getUdpDiscoveryAttributes().getUdpDiscoveryAddr(),
+                    getUdpDiscoveryAttributes().getUdpDiscoveryPort(), e );
+        }
+
+        // create a sender thread
+        sender = new UDPDiscoverySenderThread( getUdpDiscoveryAttributes(), getCacheNames() );
+    }
+
+    /**
+     * @see org.apache.commons.jcs3.engine.behavior.IRequireScheduler#setScheduledExecutorService(java.util.concurrent.ScheduledExecutorService)
+     */
+    @Override
+    public void setScheduledExecutorService(ScheduledExecutorService scheduledExecutor)
+    {
+        if (sender != null)
+        {
+            scheduledExecutor.scheduleAtFixedRate(sender, 0, 15, TimeUnit.SECONDS);
+        }
+
+        /** removes things that have been idle for too long */
+        UDPCleanupRunner cleanup = new UDPCleanupRunner( this );
+        // I'm going to use this as both, but it could happen
+        // that something could hang around twice the time using this as the
+        // delay and the idle time.
+        scheduledExecutor.scheduleAtFixedRate(cleanup, 0, getUdpDiscoveryAttributes().getMaxIdleTimeSec(), TimeUnit.SECONDS);
+    }
+
+    /**
+     * Send a passive broadcast in response to a request broadcast. Never send a request for a
+     * request. We can respond to our own requests, since a request broadcast is not intended as a
+     * connection request. We might want to only send messages, so we would send a request, but
+     * never a passive broadcast.
+     */
+    protected void serviceRequestBroadcast()
+    {
+        // create this connection each time.
+        // more robust
+        try (UDPDiscoverySender sender = new UDPDiscoverySender(
+                getUdpDiscoveryAttributes().getUdpDiscoveryAddr(),
+                getUdpDiscoveryAttributes().getUdpDiscoveryPort(),
+                getUdpDiscoveryAttributes().getUdpTTL()))
+        {
+            sender.passiveBroadcast( getUdpDiscoveryAttributes().getServiceAddress(), getUdpDiscoveryAttributes()
+                .getServicePort(), this.getCacheNames() );
+
+            // todo we should consider sending a request broadcast every so
+            // often.
+
+            log.debug( "Called sender to issue a passive broadcast" );
+        }
+        catch ( IOException e )
+        {
+            log.error( "Problem calling the UDP Discovery Sender, address [{0}] "
+                    + "port [{1}]",
+                    getUdpDiscoveryAttributes().getUdpDiscoveryAddr(),
+                    getUdpDiscoveryAttributes().getUdpDiscoveryPort(), e );
+        }
+    }
+
+    /**
+     * Adds a region to the list that is participating in discovery.
+     * <p>
+     * @param cacheName
+     */
+    public void addParticipatingCacheName( String cacheName )
+    {
+        cacheNames.add( cacheName );
+        sender.setCacheNames( getCacheNames() );
+    }
+
+    /**
+     * Removes the discovered service from the list and calls the discovery listener.
+     * <p>
+     * @param service
+     */
+    public void removeDiscoveredService( DiscoveredService service )
+    {
+        boolean contained = getDiscoveredServices().remove( service );
+
+        if ( contained )
+        {
+            log.info( "Removing {0}", service );
+        }
+
+        for (IDiscoveryListener listener : getDiscoveryListeners())
+        {
+            listener.removeDiscoveredService( service );
+        }
+    }
+
+    /**
+     * Add a service to the list. Update the held copy if we already know about it.
+     * <p>
+     * @param discoveredService discovered service
+     */
+    protected void addOrUpdateService( DiscoveredService discoveredService )
+    {
+        Set<DiscoveredService> discoveredServices = getDiscoveredServices();
+        // Since this is a set we can add it over an over.
+        // We want to replace the old one, since we may add info that is not part of the equals.
+        // The equals method on the object being added is intentionally restricted.
+        if ( !discoveredServices.contains( discoveredService ) )
+        {
+            log.info( "Set does not contain service. I discovered {0}", discoveredService );
+            log.debug( "Adding service in the set {0}", discoveredService );
+            discoveredServices.add( discoveredService );
+        }
+        else
+        {
+            log.debug( "Set contains service." );
+            log.debug( "Updating service in the set {0}", discoveredService );
+
+            // Update the list of cache names if it has changed.
+            DiscoveredService theOldServiceInformation = null;
+            // need to update the time this sucks. add has no effect convert to a map
+            for (DiscoveredService service1 : discoveredServices)
+            {
+                if ( discoveredService.equals( service1 ) )
+                {
+                    theOldServiceInformation = service1;
+                    break;
+                }
+            }
+            if ( theOldServiceInformation != null )
+            {
+                if ( !theOldServiceInformation.getCacheNames().equals(
+                        discoveredService.getCacheNames() ) )
+                {
+                    log.info( "List of cache names changed for service: {0}",
+                            discoveredService );
+                }
+            }
+
+            // replace it, we want to reset the payload and the last heard from time.
+            discoveredServices.remove( discoveredService );
+            discoveredServices.add( discoveredService );
+        }
+
+        // Always Notify the listeners
+        // If we don't do this, then if a region using the default config is initialized after notification,
+        // it will never get the service in it's no wait list.
+        // Leave it to the listeners to decide what to do.
+        for (IDiscoveryListener listener : getDiscoveryListeners())
+        {
+            listener.addDiscoveredService( discoveredService );
+        }
+    }
+
+    /**
+     * Get all the cache names we have facades for.
+     * <p>
+     * @return ArrayList
+     */
+    protected ArrayList<String> getCacheNames()
+    {
+        ArrayList<String> names = new ArrayList<>();
+        names.addAll( cacheNames );
+        return names;
+    }
+
+    /**
+     * @param attr The UDPDiscoveryAttributes to set.
+     */
+    public void setUdpDiscoveryAttributes( UDPDiscoveryAttributes attr )
+    {
+        this.udpDiscoveryAttributes = attr;
+    }
+
+    /**
+     * @return Returns the lca.
+     */
+    public UDPDiscoveryAttributes getUdpDiscoveryAttributes()
+    {
+        return this.udpDiscoveryAttributes;
+    }
+
+    /**
+     * Start necessary receiver thread
+     */
+    public void startup()
+    {
+        udpReceiverThread = new Thread(receiver);
+        udpReceiverThread.setDaemon(true);
+        // udpReceiverThread.setName( t.getName() + "--UDPReceiver" );
+        udpReceiverThread.start();
+    }
+
+    /**
+     * Shuts down the receiver.
+     */
+    @Override
+    public void shutdown()
+    {
+        if ( !shutdown )
+        {
+            shutdown = true;
+
+            // no good way to do this right now.
+            if (receiver != null)
+            {
+                log.info( "Shutting down UDP discovery service receiver." );
+                receiver.shutdown();
+                udpReceiverThread.interrupt();
+            }
+
+            if (sender != null)
+            {
+                log.info( "Shutting down UDP discovery service sender." );
+                // also call the shutdown on the sender thread itself, which
+                // will result in a remove command.
+                sender.shutdown();
+            }
+        }
+        else
+        {
+            log.debug( "Shutdown already called." );
+        }
+    }
+
+    /**
+     * @return Returns the discoveredServices.
+     */
+    public Set<DiscoveredService> getDiscoveredServices()
+    {
+        return discoveredServices;
+    }
+
+    /**
+     * @return the discoveryListeners
+     */
+    private Set<IDiscoveryListener> getDiscoveryListeners()
+    {
+        return discoveryListeners;
+    }
+
+    /**
+     * @return the discoveryListeners
+     */
+    public Set<IDiscoveryListener> getCopyOfDiscoveryListeners()
+    {
+        Set<IDiscoveryListener> copy = new HashSet<>();
+        copy.addAll( getDiscoveryListeners() );
+        return copy;
+    }
+
+    /**
+     * Adds a listener.
+     * <p>
+     * @param listener
+     * @return true if it wasn't already in the set
+     */
+    public boolean addDiscoveryListener( IDiscoveryListener listener )
+    {
+        return getDiscoveryListeners().add( listener );
+    }
+
+    /**
+     * Removes a listener.
+     * <p>
+     * @param listener
+     * @return true if it was in the set
+     */
+    public boolean removeDiscoveryListener( IDiscoveryListener listener )
+    {
+        return getDiscoveryListeners().remove( listener );
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/behavior/IDiscoveryListener.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/behavior/IDiscoveryListener.java
new file mode 100644
index 0000000..0192cd3
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/behavior/IDiscoveryListener.java
@@ -0,0 +1,44 @@
+package org.apache.commons.jcs3.utils.discovery.behavior;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.utils.discovery.DiscoveredService;
+
+/**
+ * Interface for things that want to listen to discovery events. This will allow discovery to be
+ * used outside of the TCP lateral.
+ */
+public interface IDiscoveryListener
+{
+    /**
+     * Add the service if needed. This does not necessarily mean that the service is not already
+     * added. This can be called if there is a change in service information, such as the cacheNames.
+     * <p>
+     * @param service the service to add
+     */
+    void addDiscoveredService( DiscoveredService service );
+
+    /**
+     * Remove the service from the list.
+     * <p>
+     * @param service the service to remove
+     */
+    void removeDiscoveredService( DiscoveredService service );
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/net/HostNameUtil.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/net/HostNameUtil.java
new file mode 100644
index 0000000..863388b
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/net/HostNameUtil.java
@@ -0,0 +1,195 @@
+package org.apache.commons.jcs3.utils.net;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.Enumeration;
+/*
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * Simple utility for getting the local host name.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class HostNameUtil
+{
+    /** The logger. */
+    private static final Log log = LogManager.getLog( HostNameUtil.class );
+
+    /**
+     * Gets the address for the local machine.
+     * <p>
+     * @return InetAddress.getLocalHost().getHostAddress()
+     * @throws UnknownHostException
+     */
+    public static String getLocalHostAddress() throws UnknownHostException
+    {
+        try
+        {
+            String hostAddress = getLocalHostLANAddress().getHostAddress();
+            log.debug( "hostAddress = [{0}]", hostAddress );
+            return hostAddress;
+        }
+        catch ( UnknownHostException e1 )
+        {
+            log.error( "Couldn't get localhost address", e1 );
+            throw e1;
+        }
+    }
+
+    /**
+     * Returns an <code>InetAddress</code> object encapsulating what is most likely the machine's
+     * LAN IP address.
+     * <p>
+     * This method is intended for use as a replacement of JDK method
+     * <code>InetAddress.getLocalHost</code>, because that method is ambiguous on Linux systems.
+     * Linux systems enumerate the loopback network interface the same way as regular LAN network
+     * interfaces, but the JDK <code>InetAddress.getLocalHost</code> method does not specify the
+     * algorithm used to select the address returned under such circumstances, and will often return
+     * the loopback address, which is not valid for network communication. Details <a
+     * href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4665037">here</a>.
+     * <p>
+     * This method will scan all IP addresses on all network interfaces on the host machine to
+     * determine the IP address most likely to be the machine's LAN address. If the machine has
+     * multiple IP addresses, this method will prefer a site-local IP address (e.g. 192.168.x.x or
+     * 10.10.x.x, usually IPv4) if the machine has one (and will return the first site-local address
+     * if the machine has more than one), but if the machine does not hold a site-local address,
+     * this method will return simply the first non-loopback address found (IPv4 or IPv6).</p>
+     * <p>
+     * If this method cannot find a non-loopback address using this selection algorithm, it will
+     * fall back to calling and returning the result of JDK method
+     * <code>InetAddress.getLocalHost</code>.
+     * <p>
+     * <a href="http://issues.apache.org/jira/browse/JCS-40">JIR ISSUE JCS-40</a>
+     * <p>
+     * @return InetAddress
+     * @throws UnknownHostException If the LAN address of the machine cannot be found.
+     */
+    public static InetAddress getLocalHostLANAddress()
+        throws UnknownHostException
+    {
+        try
+        {
+            InetAddress candidateAddress = null;
+            // Iterate all NICs (network interface cards)...
+            for ( Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces(); ifaces.hasMoreElements(); )
+            {
+                NetworkInterface iface = ifaces.nextElement();
+                // Iterate all IP addresses assigned to each card...
+                for ( Enumeration<InetAddress> inetAddrs = iface.getInetAddresses(); inetAddrs.hasMoreElements(); )
+                {
+                    InetAddress inetAddr = inetAddrs.nextElement();
+                    if ( !inetAddr.isLoopbackAddress() )
+                    {
+                        if ( inetAddr.isSiteLocalAddress() )
+                        {
+                            // Found non-loopback site-local address. Return it immediately...
+                            return inetAddr;
+                        }
+                        else if ( candidateAddress == null )
+                        {
+                            // Found non-loopback address, but not necessarily site-local.
+                            // Store it as a candidate to be returned if site-local address is not subsequently found...
+                            candidateAddress = inetAddr;
+                            // Note that we don't repeatedly assign non-loopback non-site-local addresses as candidates,
+                            // only the first. For subsequent iterations, candidate will be non-null.
+                        }
+                    }
+                }
+            }
+            if ( candidateAddress != null )
+            {
+                // We did not find a site-local address, but we found some other non-loopback address.
+                // Server might have a non-site-local address assigned to its NIC (or it might be running
+                // IPv6 which deprecates the "site-local" concept).
+                // Return this non-loopback candidate address...
+                return candidateAddress;
+            }
+            // At this point, we did not find a non-loopback address.
+            // Fall back to returning whatever InetAddress.getLocalHost() returns...
+            InetAddress jdkSuppliedAddress = InetAddress.getLocalHost();
+            if ( jdkSuppliedAddress == null )
+            {
+                throw new UnknownHostException( "The JDK InetAddress.getLocalHost() method unexpectedly returned null." );
+            }
+            return jdkSuppliedAddress;
+        }
+        catch ( SocketException e )
+        {
+            UnknownHostException unknownHostException = new UnknownHostException( "Failed to determine LAN address: "
+                + e );
+            unknownHostException.initCause( e );
+            throw unknownHostException;
+        }
+    }
+
+    /**
+     * On systems with multiple network interfaces and mixed IPv6/IPv4 get a valid network
+     * interface for binding to multicast
+     *
+     * @return a network interface suitable for multicast
+     * @throws SocketException if a problem occurs while reading the network interfaces
+     */
+    public static NetworkInterface getMulticastNetworkInterface() throws SocketException
+    {
+        Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
+        while (networkInterfaces.hasMoreElements())
+        {
+            NetworkInterface networkInterface = networkInterfaces.nextElement();
+            Enumeration<InetAddress> addressesFromNetworkInterface = networkInterface.getInetAddresses();
+            while (addressesFromNetworkInterface.hasMoreElements())
+            {
+                InetAddress inetAddress = addressesFromNetworkInterface.nextElement();
+                if (inetAddress.isSiteLocalAddress()
+                        && !inetAddress.isAnyLocalAddress()
+                        && !inetAddress.isLinkLocalAddress()
+                        && !inetAddress.isLoopbackAddress()
+                        && !inetAddress.isMulticastAddress())
+                {
+                    return networkInterface;
+                }
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/serialization/CompressingSerializer.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/serialization/CompressingSerializer.java
new file mode 100644
index 0000000..85ec7e5
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/serialization/CompressingSerializer.java
@@ -0,0 +1,69 @@
+package org.apache.commons.jcs3.utils.serialization;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+
+import org.apache.commons.jcs3.utils.zip.CompressionUtil;
+
+/**
+ * Performs default serialization and de-serialization. It gzips the value.
+ */
+public class CompressingSerializer extends StandardSerializer
+{
+    /**
+     * Serializes an object using default serialization. Compresses the byte array.
+     * <p>
+     * @param obj object
+     * @return byte[]
+     * @throws IOException on i/o problem
+     */
+    @Override
+    public <T> byte[] serialize( T obj )
+        throws IOException
+    {
+        byte[] uncompressed = super.serialize(obj);
+        byte[] compressed = CompressionUtil.compressByteArray( uncompressed );
+        return compressed;
+    }
+
+    /**
+     * Uses default de-serialization to turn a byte array into an object. Decompresses the value
+     * first. All exceptions are converted into IOExceptions.
+     * <p>
+     * @param data data bytes
+     * @param loader class loader to use
+     * @return Object
+     * @throws IOException on i/o problem
+     * @throws ClassNotFoundException if class is not found during deserialization
+     */
+    @Override
+    public <T> T deSerialize( byte[] data, ClassLoader loader )
+        throws IOException, ClassNotFoundException
+    {
+        if ( data == null )
+        {
+            return null;
+        }
+
+        byte[] decompressedByteArray = CompressionUtil.decompressByteArray( data );
+        return super.deSerialize(decompressedByteArray, loader);
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/serialization/SerializationConversionUtil.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/serialization/SerializationConversionUtil.java
new file mode 100644
index 0000000..f6a1ca4
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/serialization/SerializationConversionUtil.java
@@ -0,0 +1,149 @@
+package org.apache.commons.jcs3.utils.serialization;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.CacheElementSerialized;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheElementSerialized;
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * This uses a supplied Serializer to convert to and from cache elements.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class SerializationConversionUtil
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog( SerializationConversionUtil.class );
+
+    /**
+     * This returns a wrapper that has a serialized version of the value instead
+     * of the value.
+     * <p>
+     * @param element
+     * @param elementSerializer
+     *            the serializer to be used.
+     * @return null for null;
+     * @throws IOException
+     */
+    public static <K, V> ICacheElementSerialized<K, V> getSerializedCacheElement( ICacheElement<K, V> element,
+                                                                    IElementSerializer elementSerializer )
+        throws IOException
+    {
+        if ( element == null )
+        {
+            return null;
+        }
+
+        byte[] serializedValue = null;
+
+        // if it has already been serialized, don't do it again.
+        if ( element instanceof ICacheElementSerialized )
+        {
+            serializedValue = ( (ICacheElementSerialized<K, V>) element ).getSerializedValue();
+        }
+        else
+        {
+            if ( elementSerializer != null )
+            {
+                try
+                {
+                    serializedValue = elementSerializer.serialize(element.getVal());
+
+                    // update size in bytes
+                    element.getElementAttributes().setSize(serializedValue.length);
+                }
+                catch ( IOException e )
+                {
+                    log.error( "Problem serializing object.", e );
+                    throw e;
+                }
+            }
+            else
+            {
+                // we could just use the default.
+                throw new IOException( "Could not serialize object. The ElementSerializer is null." );
+            }
+        }
+        ICacheElementSerialized<K, V> serialized = new CacheElementSerialized<>(
+                element.getCacheName(), element.getKey(), serializedValue, element.getElementAttributes() );
+
+        return serialized;
+    }
+
+    /**
+     * This returns a wrapper that has a de-serialized version of the value
+     * instead of the serialized value.
+     * <p>
+     * @param serialized
+     * @param elementSerializer
+     *            the serializer to be used.
+     * @return null for null;
+     * @throws IOException
+     * @throws ClassNotFoundException
+     */
+    public static <K, V> ICacheElement<K, V> getDeSerializedCacheElement( ICacheElementSerialized<K, V> serialized,
+                                                            IElementSerializer elementSerializer )
+        throws IOException, ClassNotFoundException
+    {
+        if ( serialized == null )
+        {
+            return null;
+        }
+
+        V deSerializedValue = null;
+
+        if ( elementSerializer != null )
+        {
+            try
+            {
+                try
+                {
+                    deSerializedValue = elementSerializer.deSerialize( serialized.getSerializedValue(), null );
+                }
+                catch ( ClassNotFoundException e )
+                {
+                    log.error( "Problem de-serializing object.", e );
+                    throw e;
+                }
+            }
+            catch ( IOException e )
+            {
+                log.error( "Problem de-serializing object.", e );
+                throw e;
+            }
+        }
+        else
+        {
+            // we could just use the default.
+            throw new IOException( "Could not de-serialize object. The ElementSerializer is null." );
+       }
+        ICacheElement<K, V> deSerialized = new CacheElement<>( serialized.getCacheName(), serialized.getKey(), deSerializedValue );
+        deSerialized.setElementAttributes( serialized.getElementAttributes() );
+
+        return deSerialized;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/serialization/StandardSerializer.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/serialization/StandardSerializer.java
new file mode 100644
index 0000000..633a2c3
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/serialization/StandardSerializer.java
@@ -0,0 +1,82 @@
+package org.apache.commons.jcs3.utils.serialization;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.io.ObjectInputStreamClassLoaderAware;
+
+/**
+ * Performs default serialization and de-serialization.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class StandardSerializer
+    implements IElementSerializer
+{
+    /**
+     * Serializes an object using default serialization.
+     * <p>
+     * @param obj
+     * @return byte[]
+     * @throws IOException
+     */
+    @Override
+    public <T> byte[] serialize(T obj)
+        throws IOException
+    {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+        try (ObjectOutputStream oos = new ObjectOutputStream(baos))
+        {
+            oos.writeObject(obj);
+        }
+
+        return baos.toByteArray();
+    }
+
+    /**
+     * Uses default de-serialization to turn a byte array into an object. All exceptions are
+     * converted into IOExceptions.
+     * <p>
+     * @param data data bytes
+     * @param loader class loader to use
+     * @return Object
+     * @throws IOException
+     * @throws ClassNotFoundException
+     */
+    @Override
+    public <T> T deSerialize(byte[] data, ClassLoader loader)
+        throws IOException, ClassNotFoundException
+    {
+        try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
+             ObjectInputStream ois = new ObjectInputStreamClassLoaderAware(bais, loader))
+        {
+            @SuppressWarnings("unchecked") // Need to cast from Object
+            T readObject = (T) ois.readObject();
+            return readObject;
+        }
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/servlet/JCSServletContextListener.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/servlet/JCSServletContextListener.java
new file mode 100644
index 0000000..1b9c7f8
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/servlet/JCSServletContextListener.java
@@ -0,0 +1,73 @@
+package org.apache.commons.jcs3.utils.servlet;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * If you add this to the context listeners section of your web.xml file, this will shutdown JCS
+ * gracefully.
+ * <p>
+ * Add the following to the top of your web.xml file.
+ *
+ * <pre>
+ *  &lt;listener&gt;
+ *  &lt;listener-class&gt;
+ *  org.apache.commons.jcs3.utils.servlet.JCSServletContextListener
+ *  &lt;/listener-class&gt;
+ *  &lt;/listener&gt;
+ * </pre>
+ * @author Aaron Smuts
+ */
+public class JCSServletContextListener
+    implements ServletContextListener
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog( JCSServletContextListener.class );
+
+    /**
+     * This does nothing. We don't want to initialize the cache here.
+     * <p>
+     * @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)
+     */
+    @Override
+    public void contextInitialized( ServletContextEvent arg0 )
+    {
+        log.debug( "contextInitialized" );
+    }
+
+    /**
+     * Shutdown JCS.
+     * <p>
+     * @see javax.servlet.ServletContextListener#contextDestroyed(javax.servlet.ServletContextEvent)
+     */
+    @Override
+    public void contextDestroyed( ServletContextEvent arg0 )
+    {
+        log.debug( "contextDestroyed, shutting down JCS." );
+
+        JCS.shutdown();
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/struct/AbstractLRUMap.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/struct/AbstractLRUMap.java
new file mode 100644
index 0000000..7cfb139
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/struct/AbstractLRUMap.java
@@ -0,0 +1,542 @@
+package org.apache.commons.jcs3.utils.struct;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Collectors;
+
+import org.apache.commons.jcs3.engine.control.group.GroupAttrName;
+import org.apache.commons.jcs3.engine.stats.StatElement;
+import org.apache.commons.jcs3.engine.stats.Stats;
+import org.apache.commons.jcs3.engine.stats.behavior.IStatElement;
+import org.apache.commons.jcs3.engine.stats.behavior.IStats;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * This is a simple LRUMap. It implements most of the map methods. It is not recommended that you
+ * use any but put, get, remove, and clear.
+ * <p>
+ * Children can implement the processRemovedLRU method if they want to handle the removal of the
+ * least recently used item.
+ * <p>
+ * This class was abstracted out of the LRU Memory cache. Put, remove, and get should be thread
+ * safe. It uses a hashtable and our own double linked list.
+ * <p>
+ * Locking is done on the instance.
+ * <p>
+ * @author aaron smuts
+ */
+public abstract class AbstractLRUMap<K, V>
+    implements Map<K, V>
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog( AbstractLRUMap.class );
+
+    /** double linked list for lru */
+    private final DoubleLinkedList<LRUElementDescriptor<K, V>> list;
+
+    /** Map where items are stored by key. */
+    private final Map<K, LRUElementDescriptor<K, V>> map;
+
+    /** lock to keep map and list synchronous */
+    private final Lock lock = new ReentrantLock();
+
+    /** stats */
+    private long hitCnt = 0;
+
+    /** stats */
+    private long missCnt = 0;
+
+    /** stats */
+    private long putCnt = 0;
+
+    /**
+     * This creates an unbounded version. Setting the max objects will result in spooling on
+     * subsequent puts.
+     */
+    public AbstractLRUMap()
+    {
+        list = new DoubleLinkedList<>();
+
+        // normal hashtable is faster for
+        // sequential keys.
+        map = new ConcurrentHashMap<>();
+    }
+
+
+    /**
+     * This simply returns the number of elements in the map.
+     * <p>
+     * @see java.util.Map#size()
+     */
+    @Override
+    public int size()
+    {
+        return map.size();
+    }
+
+    /**
+     * This removes all the items. It clears the map and the double linked list.
+     * <p>
+     * @see java.util.Map#clear()
+     */
+    @Override
+    public void clear()
+    {
+        lock.lock();
+        try
+        {
+            map.clear();
+            list.removeAll();
+        }
+        finally
+        {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Returns true if the map is empty.
+     * <p>
+     * @see java.util.Map#isEmpty()
+     */
+    @Override
+    public boolean isEmpty()
+    {
+        return map.isEmpty();
+    }
+
+    /**
+     * Returns true if the map contains an element for the supplied key.
+     * <p>
+     * @see java.util.Map#containsKey(java.lang.Object)
+     */
+    @Override
+    public boolean containsKey( Object key )
+    {
+        return map.containsKey( key );
+    }
+
+    /**
+     * This is an expensive operation that determines if the object supplied is mapped to any key.
+     * <p>
+     * @see java.util.Map#containsValue(java.lang.Object)
+     */
+    @Override
+    public boolean containsValue( Object value )
+    {
+        return map.containsValue( value );
+    }
+
+    /**
+     * @return map.values();
+     */
+    @Override
+    public Collection<V> values()
+    {
+        return map.values().stream()
+                .map(value -> value.getPayload())
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * @param source
+     */
+    @Override
+    public void putAll( Map<? extends K, ? extends V> source )
+    {
+        if ( source != null )
+        {
+            source.entrySet()
+                .forEach(entry -> put(entry.getKey(), entry.getValue()));
+        }
+    }
+
+    /**
+     * @param key
+     * @return Object
+     */
+    @Override
+    public V get( Object key )
+    {
+        V retVal;
+
+        log.debug( "getting item  for key {0}", key );
+
+        LRUElementDescriptor<K, V> me = map.get( key );
+
+        if ( me == null )
+        {
+            missCnt++;
+            retVal = null;
+        }
+        else
+        {
+            hitCnt++;
+            retVal = me.getPayload();
+            list.makeFirst( me );
+        }
+
+        if ( me == null )
+        {
+            log.debug( "LRUMap miss for {0}", key );
+        }
+        else
+        {
+            log.debug( "LRUMap hit for {0}", key );
+        }
+
+        // verifyCache();
+        return retVal;
+    }
+
+    /**
+     * This gets an element out of the map without adjusting it's position in the LRU. In other
+     * words, this does not count as being used. If the element is the last item in the list, it
+     * will still be the last time in the list.
+     * <p>
+     * @param key
+     * @return Object
+     */
+    public V getQuiet( Object key )
+    {
+        V ce = null;
+        LRUElementDescriptor<K, V> me = map.get( key );
+
+        if ( me != null )
+        {
+            ce = me.getPayload();
+        }
+
+        if ( me == null )
+        {
+            log.debug( "LRUMap quiet miss for {0}", key );
+        }
+        else
+        {
+            log.debug( "LRUMap quiet hit for {0}", key );
+        }
+
+        return ce;
+    }
+
+    /**
+     * @param key
+     * @return Object removed
+     */
+    @Override
+    public V remove( Object key )
+    {
+        log.debug( "removing item for key: {0}", key );
+
+        // remove single item.
+        lock.lock();
+        try
+        {
+            LRUElementDescriptor<K, V> me = map.remove(key);
+
+            if (me != null)
+            {
+                list.remove(me);
+                return me.getPayload();
+            }
+        }
+        finally
+        {
+            lock.unlock();
+        }
+
+        return null;
+    }
+
+    /**
+     * @param key
+     * @param value
+     * @return Object
+     */
+    @Override
+    public V put(K key, V value)
+    {
+        putCnt++;
+
+        LRUElementDescriptor<K, V> old = null;
+        LRUElementDescriptor<K, V> me = new LRUElementDescriptor<>(key, value);
+
+        lock.lock();
+        try
+        {
+            list.addFirst( me );
+            old = map.put(key, me);
+
+            // If the node was the same as an existing node, remove it.
+            if ( old != null && key.equals(old.getKey()))
+            {
+                list.remove( old );
+            }
+        }
+        finally
+        {
+            lock.unlock();
+        }
+
+        // If the element limit is reached, we need to spool
+        if (shouldRemove())
+        {
+            log.debug( "In memory limit reached, removing least recently used." );
+
+            // The spool will put them in a disk event queue, so there is no
+            // need to pre-queue the queuing. This would be a bit wasteful
+            // and wouldn't save much time in this synchronous call.
+            while (shouldRemove())
+            {
+                lock.lock();
+                try
+                {
+                    LRUElementDescriptor<K, V> last = list.getLast();
+                    if (last != null)
+                    {
+                        processRemovedLRU(last.getKey(), last.getPayload());
+                        if (map.remove(last.getKey()) == null)
+                        {
+                            log.warn("update: remove failed for key: {0}",
+                                    () -> last.getKey());
+                            verifyCache();
+                        }
+                        list.removeLast();
+                    }
+                    else
+                    {
+                        verifyCache();
+                        throw new Error("update: last is null!");
+                    }
+                }
+                finally
+                {
+                    lock.unlock();
+                }
+            }
+
+            log.debug( "update: After spool map size: {0}", () -> map.size() );
+            if ( map.size() != list.size() )
+            {
+                log.error("update: After spool, size mismatch: map.size() = {0}, "
+                        + "linked list size = {1}",
+                        () -> map.size(), () -> list.size());
+            }
+        }
+
+        if ( old != null )
+        {
+            return old.getPayload();
+        }
+        return null;
+    }
+
+    protected abstract boolean shouldRemove();
+
+    /**
+     * Dump the cache entries from first to list for debugging.
+     */
+    @SuppressWarnings("unchecked") // No generics for public fields
+    public void dumpCacheEntries()
+    {
+        if (log.isTraceEnabled())
+        {
+            log.trace("dumpingCacheEntries");
+            for (LRUElementDescriptor<K, V> me = list.getFirst(); me != null; me = (LRUElementDescriptor<K, V>) me.next)
+            {
+                log.trace("dumpCacheEntries> key={0}, val={1}", me.getKey(), me.getPayload());
+            }
+        }
+    }
+
+    /**
+     * Dump the cache map for debugging.
+     */
+    public void dumpMap()
+    {
+        if (log.isTraceEnabled())
+        {
+            log.trace("dumpingMap");
+            map.entrySet().forEach(e ->
+                log.trace("dumpMap> key={0}, val={1}", e.getKey(), e.getValue().getPayload()));
+        }
+    }
+
+    /**
+     * Checks to see if all the items that should be in the cache are. Checks consistency between
+     * List and map.
+     */
+    @SuppressWarnings("unchecked") // No generics for public fields
+    protected void verifyCache()
+    {
+        if ( !log.isTraceEnabled() )
+        {
+            return;
+        }
+
+        log.trace( "verifycache: mapContains {0} elements, linked list "
+                + "contains {1} elements", map.size(), list.size() );
+        log.trace( "verifycache: checking linked list by key" );
+        for (LRUElementDescriptor<K, V> li = list.getFirst(); li != null; li = (LRUElementDescriptor<K, V>) li.next )
+        {
+            K key = li.getKey();
+            if ( !map.containsKey( key ) )
+            {
+                log.error( "verifycache: map does not contain key : {0}", li.getKey() );
+                log.error( "li.hashcode={0}", li.getKey().hashCode() );
+                log.error( "key class={0}", key.getClass() );
+                log.error( "key hashcode={0}", key.hashCode() );
+                log.error( "key toString={0}", key.toString() );
+                if ( key instanceof GroupAttrName )
+                {
+                    GroupAttrName<?> name = (GroupAttrName<?>) key;
+                    log.error( "GroupID hashcode={0}", name.groupId.hashCode() );
+                    log.error( "GroupID.class={0}", name.groupId.getClass() );
+                    log.error( "AttrName hashcode={0}", name.attrName.hashCode() );
+                    log.error( "AttrName.class={0}", name.attrName.getClass() );
+                }
+                dumpMap();
+            }
+            else if ( map.get( li.getKey() ) == null )
+            {
+                log.error( "verifycache: linked list retrieval returned null for key: {0}",
+                        li.getKey() );
+            }
+        }
+
+        log.trace( "verifycache: checking linked list by value " );
+        for (LRUElementDescriptor<K, V> li3 = list.getFirst(); li3 != null; li3 = (LRUElementDescriptor<K, V>) li3.next )
+        {
+            if ( map.containsValue( li3 ) == false )
+            {
+                log.error( "verifycache: map does not contain value : {0}", li3 );
+                dumpMap();
+            }
+        }
+
+        log.trace( "verifycache: checking via keysets!" );
+        map.forEach((key, value) -> {
+            boolean found = false;
+
+            for (LRUElementDescriptor<K, V> li2 = list.getFirst(); li2 != null; li2 = (LRUElementDescriptor<K, V>) li2.next )
+            {
+                if ( key.equals( li2.getKey() ) )
+                {
+                    found = true;
+                    break;
+                }
+            }
+            if ( !found )
+            {
+                log.error( "verifycache: key not found in list : {0}", key );
+                dumpCacheEntries();
+                if ( map.containsKey( key ) )
+                {
+                    log.error( "verifycache: map contains key" );
+                }
+                else
+                {
+                    log.error( "verifycache: map does NOT contain key, what the HECK!" );
+                }
+            }
+        });
+    }
+
+    /**
+     * This is called when an item is removed from the LRU. We just log some information.
+     * <p>
+     * Children can implement this method for special behavior.
+     * @param key
+     * @param value
+     */
+    protected void processRemovedLRU(K key, V value )
+    {
+        log.debug( "Removing key: [{0}] from LRUMap store, value = [{1}]", key, value );
+        log.debug( "LRUMap store size: \"{0}\".", this.size() );
+    }
+
+    /**
+     * @return IStats
+     */
+    public IStats getStatistics()
+    {
+        IStats stats = new Stats();
+        stats.setTypeName( "LRUMap" );
+
+        ArrayList<IStatElement<?>> elems = new ArrayList<>();
+
+        elems.add(new StatElement<>( "List Size", Integer.valueOf(list.size()) ) );
+        elems.add(new StatElement<>( "Map Size", Integer.valueOf(map.size()) ) );
+        elems.add(new StatElement<>( "Put Count", Long.valueOf(putCnt) ) );
+        elems.add(new StatElement<>( "Hit Count", Long.valueOf(hitCnt) ) );
+        elems.add(new StatElement<>( "Miss Count", Long.valueOf(missCnt) ) );
+
+        stats.setStatElements( elems );
+
+        return stats;
+    }
+
+    /**
+     * This returns a set of entries. Our LRUMapEntry is used since the value stored in the
+     * underlying map is a node in the double linked list. We wouldn't want to return this to the
+     * client, so we construct a new entry with the payload of the node.
+     * <p>
+     * TODO we should return out own set wrapper, so we can avoid the extra object creation if it
+     * isn't necessary.
+     * <p>
+     * @see java.util.Map#entrySet()
+     */
+    @Override
+    public Set<Map.Entry<K, V>> entrySet()
+    {
+        lock.lock();
+        try
+        {
+            return map.entrySet().stream()
+                    .map(entry -> new AbstractMap.SimpleEntry<>(
+                            entry.getKey(), entry.getValue().getPayload()))
+                    .collect(Collectors.toSet());
+        }
+        finally
+        {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * @return map.keySet();
+     */
+    @Override
+    public Set<K> keySet()
+    {
+        return map.values().stream()
+                .map(value -> value.getKey())
+                .collect(Collectors.toSet());
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/struct/DoubleLinkedList.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/struct/DoubleLinkedList.java
new file mode 100644
index 0000000..28d119b
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/struct/DoubleLinkedList.java
@@ -0,0 +1,291 @@
+package org.apache.commons.jcs3.utils.struct;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * This is a generic thread safe double linked list. It's very simple and all the operations are so
+ * quick that course grained synchronization is more than acceptable.
+ */
+@SuppressWarnings({ "unchecked", "rawtypes" }) // Don't know how to resolve this with generics
+public class DoubleLinkedList<T extends DoubleLinkedListNode>
+{
+    /** record size to avoid having to iterate */
+    private int size = 0;
+
+    /** The logger */
+    private static final Log log = LogManager.getLog( DoubleLinkedList.class );
+
+    /** LRU double linked list head node */
+    private T first;
+
+    /** LRU double linked list tail node */
+    private T last;
+
+    /**
+     * Default constructor.
+     */
+    public DoubleLinkedList()
+    {
+        super();
+    }
+
+    /**
+     * Adds a new node to the end of the link list.
+     * <p>
+     * @param me The feature to be added to the Last
+     */
+    public synchronized void addLast(T me)
+    {
+        if ( first == null )
+        {
+            // empty list.
+            first = me;
+        }
+        else
+        {
+            last.next = me;
+            me.prev = last;
+        }
+        last = me;
+        size++;
+    }
+
+    /**
+     * Adds a new node to the start of the link list.
+     * <p>
+     * @param me The feature to be added to the First
+     */
+    public synchronized void addFirst(T me)
+    {
+        if ( last == null )
+        {
+            // empty list.
+            last = me;
+        }
+        else
+        {
+            first.prev = me;
+            me.next = first;
+        }
+        first = me;
+        size++;
+    }
+
+    /**
+     * Returns the last node from the link list, if there are any nodes.
+     * <p>
+     * @return The last node.
+     */
+    public synchronized T getLast()
+    {
+        log.debug( "returning last node" );
+        return last;
+    }
+
+    /**
+     * Removes the specified node from the link list.
+     * <p>
+     * @return DoubleLinkedListNode, the first node.
+     */
+    public synchronized T getFirst()
+    {
+        log.debug( "returning first node" );
+        return first;
+    }
+
+    /**
+     * Moves an existing node to the start of the link list.
+     * <p>
+     * @param ln The node to set as the head.
+     */
+    public synchronized void makeFirst(T ln)
+    {
+        if ( ln.prev == null )
+        {
+            // already the first node. or not a node
+            return;
+        }
+        // splice: remove it from the list
+        ln.prev.next = ln.next;
+
+        if ( ln.next == null )
+        {
+            // last but not the first.
+            last = (T) ln.prev;
+            last.next = null;
+        }
+        else
+        {
+            // neither the last nor the first.
+            ln.next.prev = ln.prev;
+        }
+        first.prev = ln;
+        ln.next = first;
+        ln.prev = null;
+        first = ln;
+    }
+
+    /**
+     * Moves an existing node to the end of the link list.
+     * <p>
+     * @param ln The node to set as the head.
+     */
+    public synchronized void makeLast(T ln)
+    {
+        if ( ln.next == null )
+        {
+            // already the last node. or not a node
+            return;
+        }
+        // splice: remove it from the list
+        if ( ln.prev != null )
+        {
+            ln.prev.next = ln.next;
+        }
+        else
+        {
+            // first
+            first = last;
+        }
+
+        if ( last != null )
+        {
+            last.next = ln;
+        }
+        ln.prev = last;
+        ln.next = null;
+        last = ln;
+    }
+
+    /**
+     * Remove all of the elements from the linked list implementation.
+     */
+    public synchronized void removeAll()
+    {
+        for (T me = first; me != null; )
+        {
+            if ( me.prev != null )
+            {
+                me.prev = null;
+            }
+            T next = (T) me.next;
+            me = next;
+        }
+        first = last = null;
+        // make sure this will work, could be add while this is happening.
+        size = 0;
+    }
+
+    /**
+     * Removes the specified node from the link list.
+     * <p>
+     * @param me Description of the Parameter
+     * @return true if an element was removed.
+     */
+    public synchronized boolean remove(T me)
+    {
+        log.debug( "removing node" );
+
+        if ( me.next == null )
+        {
+            if ( me.prev == null )
+            {
+                // Make sure it really is the only node before setting head and
+                // tail to null. It is possible that we will be passed a node
+                // which has already been removed from the list, in which case
+                // we should ignore it
+
+                if ( me == first && me == last )
+                {
+                    first = last = null;
+                }
+            }
+            else
+            {
+                // last but not the first.
+                last = (T) me.prev;
+                last.next = null;
+                me.prev = null;
+            }
+        }
+        else if ( me.prev == null )
+        {
+            // first but not the last.
+            first = (T) me.next;
+            first.prev = null;
+            me.next = null;
+        }
+        else
+        {
+            // neither the first nor the last.
+            me.prev.next = me.next;
+            me.next.prev = me.prev;
+            me.prev = me.next = null;
+        }
+        size--;
+
+        return true;
+    }
+
+    /**
+     * Removes the specified node from the link list.
+     * <p>
+     * @return The last node if there was one to remove.
+     */
+    public synchronized T removeLast()
+    {
+        log.debug( "removing last node" );
+        T temp = last;
+        if ( last != null )
+        {
+            remove( last );
+        }
+        return temp;
+    }
+
+    /**
+     * Returns the size of the list.
+     * <p>
+     * @return int
+     */
+    public synchronized int size()
+    {
+        return size;
+    }
+
+    // ///////////////////////////////////////////////////////////////////
+    /**
+     * Dump the cache entries from first to list for debugging.
+     */
+    public synchronized void debugDumpEntries()
+    {
+        if ( log.isDebugEnabled() )
+        {
+            log.debug( "dumping Entries" );
+            for (T me = first; me != null; me = (T) me.next)
+            {
+                log.debug( "dump Entries> payload= \"{0}\"", me.getPayload() );
+            }
+        }
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/struct/DoubleLinkedListNode.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/struct/DoubleLinkedListNode.java
new file mode 100644
index 0000000..3f55041
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/struct/DoubleLinkedListNode.java
@@ -0,0 +1,62 @@
+package org.apache.commons.jcs3.utils.struct;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.Serializable;
+
+/**
+ * This serves as a placeholder in a double linked list. You can extend this to
+ * add functionality. This allows you to remove in constant time from a linked
+ * list.
+ * <p>
+ * It simply holds the payload and a reference to the items before and after it
+ * in the list.
+ */
+public class DoubleLinkedListNode<T>
+    implements Serializable
+{
+    /** Dont' change. */
+    private static final long serialVersionUID = -1114934407695836097L;
+
+    /** The object in the node. */
+    private final T payload;
+
+    /** Double Linked list references */
+    public DoubleLinkedListNode<T> prev;
+
+    /** Double Linked list references */
+    public DoubleLinkedListNode<T> next;
+
+    /**
+     * @param payloadP
+     */
+    public DoubleLinkedListNode(T payloadP)
+    {
+        payload = payloadP;
+    }
+
+    /**
+     * @return Object
+     */
+    public T getPayload()
+    {
+        return payload;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/struct/LRUElementDescriptor.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/struct/LRUElementDescriptor.java
new file mode 100644
index 0000000..4a23554
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/struct/LRUElementDescriptor.java
@@ -0,0 +1,60 @@
+package org.apache.commons.jcs3.utils.struct;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * This is a node in the double linked list. It is stored as the value in the underlying map used by
+ * the LRUMap class.
+ */
+public class LRUElementDescriptor<K, V>
+    extends DoubleLinkedListNode<V>
+{
+    /** Don't change. */
+    private static final long serialVersionUID = 8249555756363020156L;
+
+    /** The key value */
+    private K key;
+
+    /**
+     * @param key
+     * @param payloadP
+     */
+    public LRUElementDescriptor(K key, V payloadP)
+    {
+        super(payloadP);
+        this.setKey(key);
+    }
+
+    /**
+     * @param key The key to set.
+     */
+    public void setKey(K key)
+    {
+        this.key = key;
+    }
+
+    /**
+     * @return Returns the key.
+     */
+    public K getKey()
+    {
+        return key;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/struct/LRUMap.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/struct/LRUMap.java
new file mode 100644
index 0000000..d654c9d
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/struct/LRUMap.java
@@ -0,0 +1,57 @@
+package org.apache.commons.jcs3.utils.struct;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ *
+ * @author Wiktor Niesiobędzki
+ *
+ *         Simple LRUMap implementation that keeps the number of the objects below or equal maxObjects
+ *
+ * @param <K>
+ * @param <V>
+ */
+public class LRUMap<K, V> extends AbstractLRUMap<K, V>
+{
+    /** if the max is less than 0, there is no limit! */
+    private int maxObjects = -1;
+
+    public LRUMap()
+    {
+        super();
+    }
+
+    /**
+     *
+     * @param maxObjects
+     *            maximum number to keep in the map
+     */
+    public LRUMap(int maxObjects)
+    {
+        this();
+        this.maxObjects = maxObjects;
+    }
+
+    @Override
+    public boolean shouldRemove()
+    {
+        return maxObjects > 0 && this.size() > maxObjects;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/threadpool/DaemonThreadFactory.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/threadpool/DaemonThreadFactory.java
new file mode 100644
index 0000000..c559bee
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/threadpool/DaemonThreadFactory.java
@@ -0,0 +1,74 @@
+package org.apache.commons.jcs3.utils.threadpool;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * Allows us to set the daemon status on the threads.
+ * <p>
+ * @author aaronsm
+ */
+public class DaemonThreadFactory
+    implements ThreadFactory
+{
+    private final String prefix;
+    private final boolean threadIsDaemon = true;
+    private int threadPriority = Thread.NORM_PRIORITY;
+
+    /**
+     * Constructor
+     *
+     * @param prefix thread name prefix
+     */
+    public DaemonThreadFactory(String prefix)
+    {
+        this(prefix, Thread.NORM_PRIORITY);
+    }
+
+    /**
+     * Constructor
+     *
+     * @param prefix thread name prefix
+     * @param threadPriority set thread priority
+     */
+    public DaemonThreadFactory(String prefix, int threadPriority)
+    {
+        this.prefix = prefix;
+        this.threadPriority = threadPriority;
+    }
+
+    /**
+     * Sets the thread to daemon.
+     * <p>
+     * @param runner
+     * @return a daemon thread
+     */
+    @Override
+    public Thread newThread( Runnable runner )
+    {
+        Thread t = new Thread( runner );
+        String oldName = t.getName();
+        t.setName( prefix + oldName );
+        t.setDaemon(threadIsDaemon);
+        t.setPriority(threadPriority);
+        return t;
+    }
+}
\ No newline at end of file
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/threadpool/PoolConfiguration.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/threadpool/PoolConfiguration.java
new file mode 100644
index 0000000..c779dc3
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/threadpool/PoolConfiguration.java
@@ -0,0 +1,293 @@
+package org.apache.commons.jcs3.utils.threadpool;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * This object holds configuration data for a thread pool.
+ * <p>
+ * @author Aaron Smuts
+ */
+public final class PoolConfiguration
+    implements Cloneable
+{
+    /**
+     * DEFAULT SETTINGS
+     */
+    private static final boolean DEFAULT_USE_BOUNDARY = true;
+
+    /** Default queue size limit */
+    private static final int DEFAULT_BOUNDARY_SIZE = 2000;
+
+    /** Default max size */
+    private static final int DEFAULT_MAXIMUM_POOL_SIZE = 150;
+
+    /** Default min */
+    private static final int DEFAULT_MINIMUM_POOL_SIZE = Runtime.getRuntime().availableProcessors();
+
+    /** Default keep alive */
+    private static final int DEFAULT_KEEPALIVE_TIME = 1000 * 60 * 5;
+
+    /** Default when blocked */
+    private static final WhenBlockedPolicy DEFAULT_WHEN_BLOCKED_POLICY = WhenBlockedPolicy.RUN;
+
+    /** Default startup size */
+    private static final int DEFAULT_STARTUP_SIZE = DEFAULT_MINIMUM_POOL_SIZE;
+
+    /** Should we bound the queue */
+    private boolean useBoundary = DEFAULT_USE_BOUNDARY;
+
+    /** If the queue is bounded, how big can it get */
+    private int boundarySize = DEFAULT_BOUNDARY_SIZE;
+
+    /** only has meaning if a boundary is used */
+    private int maximumPoolSize = DEFAULT_MAXIMUM_POOL_SIZE;
+
+    /**
+     * the exact number that will be used in a boundless queue. If the queue has a boundary, more
+     * will be created if the queue fills.
+     */
+    private int minimumPoolSize = DEFAULT_MINIMUM_POOL_SIZE;
+
+    /** How long idle threads above the minimum should be kept alive. */
+    private int keepAliveTime = DEFAULT_KEEPALIVE_TIME;
+
+    public enum WhenBlockedPolicy {
+        /** abort when queue is full and max threads is reached. */
+        ABORT,
+
+        /** block when queue is full and max threads is reached. */
+        BLOCK,
+
+        /** run in current thread when queue is full and max threads is reached. */
+        RUN,
+
+        /** wait when queue is full and max threads is reached. */
+        WAIT,
+
+        /** discard oldest when queue is full and max threads is reached. */
+        DISCARDOLDEST
+    }
+
+    /** should be ABORT, BLOCK, RUN, WAIT, DISCARDOLDEST, */
+    private WhenBlockedPolicy whenBlockedPolicy = DEFAULT_WHEN_BLOCKED_POLICY;
+
+    /** The number of threads to create on startup */
+    private int startUpSize = DEFAULT_MINIMUM_POOL_SIZE;
+
+    /**
+     * @param useBoundary The useBoundary to set.
+     */
+    public void setUseBoundary( boolean useBoundary )
+    {
+        this.useBoundary = useBoundary;
+    }
+
+    /**
+     * @return Returns the useBoundary.
+     */
+    public boolean isUseBoundary()
+    {
+        return useBoundary;
+    }
+
+    /**
+     * Default
+     */
+    public PoolConfiguration()
+    {
+        this( DEFAULT_USE_BOUNDARY, DEFAULT_BOUNDARY_SIZE, DEFAULT_MAXIMUM_POOL_SIZE,
+              DEFAULT_MINIMUM_POOL_SIZE, DEFAULT_KEEPALIVE_TIME,
+              DEFAULT_WHEN_BLOCKED_POLICY, DEFAULT_STARTUP_SIZE );
+    }
+
+    /**
+     * Construct a completely configured instance.
+     * <p>
+     * @param useBoundary
+     * @param boundarySize
+     * @param maximumPoolSize
+     * @param minimumPoolSize
+     * @param keepAliveTime
+     * @param whenBlockedPolicy
+     * @param startUpSize
+     */
+    public PoolConfiguration( boolean useBoundary, int boundarySize, int maximumPoolSize, int minimumPoolSize,
+                              int keepAliveTime, WhenBlockedPolicy whenBlockedPolicy, int startUpSize )
+    {
+        setUseBoundary( useBoundary );
+        setBoundarySize( boundarySize );
+        setMaximumPoolSize( maximumPoolSize );
+        setMinimumPoolSize( minimumPoolSize );
+        setKeepAliveTime( keepAliveTime );
+        setWhenBlockedPolicy( whenBlockedPolicy );
+        setStartUpSize( startUpSize );
+    }
+
+    /**
+     * @param boundarySize The boundarySize to set.
+     */
+    public void setBoundarySize( int boundarySize )
+    {
+        this.boundarySize = boundarySize;
+    }
+
+    /**
+     * @return Returns the boundarySize.
+     */
+    public int getBoundarySize()
+    {
+        return boundarySize;
+    }
+
+    /**
+     * @param maximumPoolSize The maximumPoolSize to set.
+     */
+    public void setMaximumPoolSize( int maximumPoolSize )
+    {
+        this.maximumPoolSize = maximumPoolSize;
+    }
+
+    /**
+     * @return Returns the maximumPoolSize.
+     */
+    public int getMaximumPoolSize()
+    {
+        return maximumPoolSize;
+    }
+
+    /**
+     * @param minimumPoolSize The minimumPoolSize to set.
+     */
+    public void setMinimumPoolSize( int minimumPoolSize )
+    {
+        this.minimumPoolSize = minimumPoolSize;
+    }
+
+    /**
+     * @return Returns the minimumPoolSize.
+     */
+    public int getMinimumPoolSize()
+    {
+        return minimumPoolSize;
+    }
+
+    /**
+     * @param keepAliveTime The keepAliveTime to set.
+     */
+    public void setKeepAliveTime( int keepAliveTime )
+    {
+        this.keepAliveTime = keepAliveTime;
+    }
+
+    /**
+     * @return Returns the keepAliveTime.
+     */
+    public int getKeepAliveTime()
+    {
+        return keepAliveTime;
+    }
+
+    /**
+     * @param whenBlockedPolicy The whenBlockedPolicy to set.
+     */
+    public void setWhenBlockedPolicy( String whenBlockedPolicy )
+    {
+        if ( whenBlockedPolicy != null )
+        {
+            WhenBlockedPolicy policy = WhenBlockedPolicy.valueOf(whenBlockedPolicy.trim().toUpperCase());
+            setWhenBlockedPolicy(policy);
+        }
+        else
+        {
+            // the value is null, default to RUN
+            this.whenBlockedPolicy = WhenBlockedPolicy.RUN;
+        }
+    }
+
+    /**
+     * @param whenBlockedPolicy The whenBlockedPolicy to set.
+     */
+    public void setWhenBlockedPolicy( WhenBlockedPolicy whenBlockedPolicy )
+    {
+        if ( whenBlockedPolicy != null )
+        {
+            this.whenBlockedPolicy = whenBlockedPolicy;
+        }
+        else
+        {
+            // the value is null, default to RUN
+            this.whenBlockedPolicy = WhenBlockedPolicy.RUN;
+        }
+    }
+
+    /**
+     * @return Returns the whenBlockedPolicy.
+     */
+    public WhenBlockedPolicy getWhenBlockedPolicy()
+    {
+        return whenBlockedPolicy;
+    }
+
+    /**
+     * @param startUpSize The startUpSize to set.
+     */
+    public void setStartUpSize( int startUpSize )
+    {
+        this.startUpSize = startUpSize;
+    }
+
+    /**
+     * @return Returns the startUpSize.
+     */
+    public int getStartUpSize()
+    {
+        return startUpSize;
+    }
+
+    /**
+     * To string for debugging purposes.
+     * @return String
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        buf.append( "useBoundary = [" + isUseBoundary() + "] " );
+        buf.append( "boundarySize = [" + boundarySize + "] " );
+        buf.append( "maximumPoolSize = [" + maximumPoolSize + "] " );
+        buf.append( "minimumPoolSize = [" + minimumPoolSize + "] " );
+        buf.append( "keepAliveTime = [" + keepAliveTime + "] " );
+        buf.append( "whenBlockedPolicy = [" + getWhenBlockedPolicy() + "] " );
+        buf.append( "startUpSize = [" + startUpSize + "]" );
+        return buf.toString();
+    }
+
+    /**
+     * Copies the instance variables to another instance.
+     * <p>
+     * @return PoolConfiguration
+     */
+    @Override
+    public PoolConfiguration clone()
+    {
+        return new PoolConfiguration( isUseBoundary(), boundarySize, maximumPoolSize, minimumPoolSize, keepAliveTime,
+                                      getWhenBlockedPolicy(), startUpSize );
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/threadpool/ThreadPoolManager.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/threadpool/ThreadPoolManager.java
new file mode 100644
index 0000000..4552f9a
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/threadpool/ThreadPoolManager.java
@@ -0,0 +1,338 @@
+package org.apache.commons.jcs3.utils.threadpool;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.config.PropertySetter;
+
+/**
+ * This manages threadpools for an application
+ * <p>
+ * It is a singleton since threads need to be managed vm wide.
+ * <p>
+ * This manager forces you to use a bounded queue. By default it uses the current thread for
+ * execution when the buffer is full and no free threads can be created.
+ * <p>
+ * You can specify the props file to use or pass in a properties object prior to configuration.
+ * <p>
+ * If set, the Properties object will take precedence.
+ * <p>
+ * If a value is not set for a particular pool, the hard coded defaults in <code>PoolConfiguration</code> will be used.
+ * You can configure default settings by specifying <code>thread_pool.default</code> in the properties, ie "cache.ccf"
+ * <p>
+ * @author Aaron Smuts
+ */
+public class ThreadPoolManager
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog( ThreadPoolManager.class );
+
+    /** The default config, created using property defaults if present, else those above. */
+    private PoolConfiguration defaultConfig;
+
+    /** The default scheduler config, created using property defaults if present, else those above. */
+    private PoolConfiguration defaultSchedulerConfig;
+
+    /** the root property name */
+    private static final String PROP_NAME_ROOT = "thread_pool";
+
+    /** default property file name */
+    private static final String DEFAULT_PROP_NAME_ROOT = "thread_pool.default";
+
+    /** the scheduler root property name */
+    private static final String PROP_NAME_SCHEDULER_ROOT = "scheduler_pool";
+
+    /** default scheduler property file name */
+    private static final String DEFAULT_PROP_NAME_SCHEDULER_ROOT = "scheduler_pool.default";
+
+   /**
+     * You can specify the properties to be used to configure the thread pool. Setting this post
+     * initialization will have no effect.
+     */
+    private static volatile Properties props = null;
+
+    /** Map of names to pools. */
+    private final ConcurrentHashMap<String, ExecutorService> pools;
+
+    /** Map of names to scheduler pools. */
+    private final ConcurrentHashMap<String, ScheduledExecutorService> schedulerPools;
+
+    /**
+     * The ThreadPoolManager instance (holder pattern)
+     */
+    private static class ThreadPoolManagerHolder
+    {
+        static final ThreadPoolManager INSTANCE = new ThreadPoolManager();
+    }
+
+    /**
+     * No instances please. This is a singleton.
+     */
+    private ThreadPoolManager()
+    {
+        this.pools = new ConcurrentHashMap<>();
+        this.schedulerPools = new ConcurrentHashMap<>();
+        configure();
+    }
+
+    /**
+     * Creates a pool based on the configuration info.
+     * <p>
+     * @param config the pool configuration
+     * @param threadNamePrefix prefix for the thread names of the pool
+     * @return A ThreadPool wrapper
+     */
+    public ExecutorService createPool( PoolConfiguration config, String threadNamePrefix)
+    {
+    	return createPool(config, threadNamePrefix, Thread.NORM_PRIORITY);
+    }
+
+    /**
+     * Creates a pool based on the configuration info.
+     * <p>
+     * @param config the pool configuration
+     * @param threadNamePrefix prefix for the thread names of the pool
+     * @param threadPriority the priority of the created threads
+     * @return A ThreadPool wrapper
+     */
+    public ExecutorService createPool( PoolConfiguration config, String threadNamePrefix, int threadPriority )
+    {
+        BlockingQueue<Runnable> queue = null;
+        if ( config.isUseBoundary() )
+        {
+            log.debug( "Creating a Bounded Buffer to use for the pool" );
+            queue = new LinkedBlockingQueue<>(config.getBoundarySize());
+        }
+        else
+        {
+            log.debug( "Creating a non bounded Linked Queue to use for the pool" );
+            queue = new LinkedBlockingQueue<>();
+        }
+
+        ThreadPoolExecutor pool = new ThreadPoolExecutor(
+            config.getStartUpSize(),
+            config.getMaximumPoolSize(),
+            config.getKeepAliveTime(),
+            TimeUnit.MILLISECONDS,
+            queue,
+            new DaemonThreadFactory(threadNamePrefix, threadPriority));
+
+        // when blocked policy
+        switch (config.getWhenBlockedPolicy())
+        {
+            case ABORT:
+                pool.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
+                break;
+
+            case RUN:
+                pool.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+                break;
+
+            case WAIT:
+                throw new RuntimeException("POLICY_WAIT no longer supported");
+
+            case DISCARDOLDEST:
+                pool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
+                break;
+
+            default:
+                break;
+        }
+
+        pool.prestartAllCoreThreads();
+
+        return pool;
+    }
+
+    /**
+     * Creates a scheduler pool based on the configuration info.
+     * <p>
+     * @param config the pool configuration
+     * @param threadNamePrefix prefix for the thread names of the pool
+     * @param threadPriority the priority of the created threads
+     * @return A ScheduledExecutorService
+     */
+    public ScheduledExecutorService createSchedulerPool( PoolConfiguration config, String threadNamePrefix, int threadPriority )
+    {
+    	ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(
+    			config.getMaximumPoolSize(),
+    			new DaemonThreadFactory(threadNamePrefix, threadPriority));
+
+        return scheduler;
+    }
+
+    /**
+     * Returns a configured instance of the ThreadPoolManger To specify a configuration file or
+     * Properties object to use call the appropriate setter prior to calling getInstance.
+     * <p>
+     * @return The single instance of the ThreadPoolManager
+     */
+    public static ThreadPoolManager getInstance()
+    {
+        return ThreadPoolManagerHolder.INSTANCE;
+    }
+
+    /**
+     * Dispose of the instance of the ThreadPoolManger and shut down all thread pools
+     */
+    public static void dispose()
+    {
+        for ( Iterator<Map.Entry<String, ExecutorService>> i =
+                getInstance().pools.entrySet().iterator(); i.hasNext(); )
+        {
+            Map.Entry<String, ExecutorService> entry = i.next();
+            try
+            {
+                entry.getValue().shutdownNow();
+            }
+            catch (Throwable t)
+            {
+                log.warn("Failed to close pool {0}", entry.getKey(), t);
+            }
+            i.remove();
+        }
+
+        for ( Iterator<Map.Entry<String, ScheduledExecutorService>> i =
+                getInstance().schedulerPools.entrySet().iterator(); i.hasNext(); )
+        {
+            Map.Entry<String, ScheduledExecutorService> entry = i.next();
+            try
+            {
+                entry.getValue().shutdownNow();
+            }
+            catch (Throwable t)
+            {
+                log.warn("Failed to close pool {0}", entry.getKey(), t);
+            }
+            i.remove();
+        }
+    }
+
+    /**
+     * Returns an executor service by name. If a service by this name does not exist in the configuration file or
+     * properties, one will be created using the default values.
+     * <p>
+     * Services are lazily created.
+     * <p>
+     * @param name
+     * @return The executor service configured for the name.
+     */
+    public ExecutorService getExecutorService( String name )
+    {
+    	ExecutorService pool = pools.computeIfAbsent(name, key -> {
+            log.debug( "Creating pool for name [{0}]", key );
+            PoolConfiguration config = loadConfig( PROP_NAME_ROOT + "." + key, defaultConfig );
+            return createPool( config, "JCS-ThreadPoolManager-" + key + "-" );
+    	});
+
+        return pool;
+    }
+
+    /**
+     * Returns a scheduler pool by name. If a pool by this name does not exist in the configuration file or
+     * properties, one will be created using the default values.
+     * <p>
+     * Pools are lazily created.
+     * <p>
+     * @param name
+     * @return The scheduler pool configured for the name.
+     */
+    public ScheduledExecutorService getSchedulerPool( String name )
+    {
+    	ScheduledExecutorService pool = schedulerPools.computeIfAbsent(name, key -> {
+            log.debug( "Creating scheduler pool for name [{0}]", key );
+            PoolConfiguration config = loadConfig( PROP_NAME_SCHEDULER_ROOT + "." + key,
+                    defaultSchedulerConfig );
+            return createSchedulerPool( config, "JCS-ThreadPoolManager-" + key + "-", Thread.NORM_PRIORITY );
+    	});
+
+        return pool;
+    }
+
+    /**
+     * Returns the names of all configured pools.
+     * <p>
+     * @return ArrayList of string names
+     */
+    protected Set<String> getPoolNames()
+    {
+        return pools.keySet();
+    }
+
+    /**
+     * This will be used if it is not null on initialization. Setting this post initialization will
+     * have no effect.
+     * <p>
+     * @param props The props to set.
+     */
+    public static void setProps( Properties props )
+    {
+        ThreadPoolManager.props = props;
+    }
+
+    /**
+     * Initialize the ThreadPoolManager and create all the pools defined in the configuration.
+     */
+    private void configure()
+    {
+        log.debug( "Initializing ThreadPoolManager" );
+
+        if ( props == null )
+        {
+            log.warn( "No configuration settings found. Using hardcoded default values for all pools." );
+            props = new Properties();
+        }
+
+        // set initial default and then override if new settings are available
+        defaultConfig = loadConfig( DEFAULT_PROP_NAME_ROOT, new PoolConfiguration() );
+        defaultSchedulerConfig = loadConfig( DEFAULT_PROP_NAME_SCHEDULER_ROOT, new PoolConfiguration() );
+    }
+
+    /**
+     * Configures the PoolConfiguration settings.
+     * <p>
+     * @param root the configuration key prefix
+     * @param defaultPoolConfiguration the default configuration
+     * @return PoolConfiguration
+     */
+    private PoolConfiguration loadConfig( String root, PoolConfiguration defaultPoolConfiguration )
+    {
+        PoolConfiguration config = defaultPoolConfiguration.clone();
+        PropertySetter.setProperties( config, props, root + "." );
+
+        log.debug( "{0} PoolConfiguration = {1}", root, config );
+
+        return config;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/timing/ElapsedTimer.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/timing/ElapsedTimer.java
new file mode 100644
index 0000000..f9b7fc8
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/timing/ElapsedTimer.java
@@ -0,0 +1,58 @@
+package org.apache.commons.jcs3.utils.timing;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * This is a simple timer utility.
+ */
+public class ElapsedTimer
+{
+    /** display suffix describing the unit of measure. */
+    private static final String SUFFIX = "ms.";
+
+    /**
+     * Sets the start time when created.
+     */
+    private long timeStamp = System.currentTimeMillis();
+
+    /**
+     * Gets the time elapsed between the start time and now. The start time is reset to now.
+     * Subsequent calls will get the time between then and now.
+     * <p>
+     * @return the elapsed time
+     */
+    public long getElapsedTime()
+    {
+        long now = System.currentTimeMillis();
+        long elapsed = now - timeStamp;
+        timeStamp = now;
+        return elapsed;
+    }
+
+    /**
+     * Returns the elapsed time with the display suffix.
+     * <p>
+     * @return formatted elapsed Time
+     */
+    public String getElapsedTimeString()
+    {
+        return String.valueOf( getElapsedTime() ) + SUFFIX;
+    }
+}
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/zip/CompressionUtil.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/zip/CompressionUtil.java
new file mode 100644
index 0000000..9b67c32
--- /dev/null
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/zip/CompressionUtil.java
@@ -0,0 +1,203 @@
+package org.apache.commons.jcs3.utils.zip;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.zip.DataFormatException;
+import java.util.zip.Deflater;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.Inflater;
+
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/** Compress / Decompress. */
+public final class CompressionUtil
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog( CompressionUtil.class );
+
+    /**
+     * no instances.
+     */
+    private CompressionUtil()
+    {
+        // NO OP
+    }
+
+    /**
+     * Decompress the byte array passed using a default buffer length of 1024.
+     * <p>
+     * @param input compressed byte array webservice response
+     * @return uncompressed byte array
+     */
+    public static byte[] decompressByteArray( final byte[] input )
+    {
+        return decompressByteArray( input, 1024 );
+    }
+
+    /**
+     * Decompress the byte array passed
+     * <p>
+     * @param input compressed byte array webservice response
+     * @param bufferLength buffer length
+     * @return uncompressed byte array
+     */
+    public static byte[] decompressByteArray( final byte[] input, final int bufferLength )
+    {
+        if ( null == input )
+        {
+            throw new IllegalArgumentException( "Input was null" );
+        }
+
+        // Create the decompressor and give it the data to compress
+        final Inflater decompressor = new Inflater();
+
+        decompressor.setInput( input );
+
+        // Create an expandable byte array to hold the decompressed data
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream( input.length );
+
+        // Decompress the data
+        final byte[] buf = new byte[bufferLength];
+
+        try
+        {
+            while ( !decompressor.finished() )
+            {
+                int count = decompressor.inflate( buf );
+                baos.write( buf, 0, count );
+            }
+        }
+        catch ( DataFormatException ex )
+        {
+            log.error( "Problem decompressing.", ex );
+        }
+
+        decompressor.end();
+
+        try
+        {
+            baos.close();
+        }
+        catch ( IOException ex )
+        {
+            log.error( "Problem closing stream.", ex );
+        }
+
+        return baos.toByteArray();
+    }
+
+    /**
+     * Compress the byte array passed
+     * <p>
+     * @param input byte array
+     * @return compressed byte array
+     * @throws IOException thrown if we can't close the output stream
+     */
+    public static byte[] compressByteArray( byte[] input )
+        throws IOException
+    {
+        return compressByteArray( input, 1024 );
+    }
+
+    /**
+     * Compress the byte array passed
+     * <p>
+     * @param input byte array
+     * @param bufferLength buffer length
+     * @return compressed byte array
+     * @throws IOException thrown if we can't close the output stream
+     */
+    public static byte[] compressByteArray( byte[] input, int bufferLength )
+        throws IOException
+    {
+        // Compressor with highest level of compression
+        Deflater compressor = new Deflater();
+        compressor.setLevel( Deflater.BEST_COMPRESSION );
+
+        // Give the compressor the data to compress
+        compressor.setInput( input );
+        compressor.finish();
+
+        // Create an expandable byte array to hold the compressed data.
+        // It is not necessary that the compressed data will be smaller than
+        // the uncompressed data.
+        ByteArrayOutputStream bos = new ByteArrayOutputStream( input.length );
+
+        // Compress the data
+        byte[] buf = new byte[bufferLength];
+        while ( !compressor.finished() )
+        {
+            int count = compressor.deflate( buf );
+            bos.write( buf, 0, count );
+        }
+
+        // JCS-136 ( Details here : http://www.devguli.com/blog/eng/java-deflater-and-outofmemoryerror/ )
+        compressor.end();
+        bos.close();
+
+        // Get the compressed data
+        return bos.toByteArray();
+
+    }
+
+    /**
+     * decompress a gzip byte array, using a default buffer length of 1024
+     * <p>
+     * @param compressedByteArray gzip-compressed byte array
+     * @return decompressed byte array
+     * @throws IOException thrown if there was a failure to construct the GzipInputStream
+     */
+    public static byte[] decompressGzipByteArray( byte[] compressedByteArray )
+        throws IOException
+    {
+        return decompressGzipByteArray( compressedByteArray, 1024 );
+    }
+
+    /**
+     * decompress a gzip byte array, using a default buffer length of 1024
+     * <p>
+     * @param compressedByteArray gzip-compressed byte array
+     * @param bufferlength size of the buffer in bytes
+     * @return decompressed byte array
+     * @throws IOException thrown if there was a failure to construct the GzipInputStream
+     */
+    public static byte[] decompressGzipByteArray( byte[] compressedByteArray, int bufferlength )
+        throws IOException
+    {
+        ByteArrayOutputStream uncompressedStream = new ByteArrayOutputStream();
+
+        GZIPInputStream compressedStream = new GZIPInputStream( new ByteArrayInputStream( compressedByteArray ) );
+
+        byte[] buffer = new byte[bufferlength];
+
+        int index = -1;
+
+        while ( ( index = compressedStream.read( buffer ) ) != -1 )
+        {
+            uncompressedStream.write( buffer, 0, index );
+        }
+
+        return uncompressedStream.toByteArray();
+    }
+}
diff --git a/commons-jcs-core/src/main/resources/META-INF/services/org.apache.commons.jcs.log.LogFactory b/commons-jcs-core/src/main/resources/META-INF/services/org.apache.commons.jcs.log.LogFactory
deleted file mode 100644
index c34a93d..0000000
--- a/commons-jcs-core/src/main/resources/META-INF/services/org.apache.commons.jcs.log.LogFactory
+++ /dev/null
@@ -1,16 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache license, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the license for the specific language governing permissions and
-# limitations under the license.
-org.apache.commons.jcs.log.JulLogFactory
-org.apache.commons.jcs.log.Log4j2Factory
diff --git a/commons-jcs-core/src/main/resources/META-INF/services/org.apache.commons.jcs3.log.LogFactory b/commons-jcs-core/src/main/resources/META-INF/services/org.apache.commons.jcs3.log.LogFactory
new file mode 100644
index 0000000..2a8bea7
--- /dev/null
+++ b/commons-jcs-core/src/main/resources/META-INF/services/org.apache.commons.jcs3.log.LogFactory
@@ -0,0 +1,16 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache license, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the license for the specific language governing permissions and
+# limitations under the license.
+org.apache.commons.jcs3.log.JulLogFactory
+org.apache.commons.jcs3.log.Log4j2Factory
diff --git a/commons-jcs-core/src/test/conf/cache.ccf b/commons-jcs-core/src/test/conf/cache.ccf
index bbe3853..239c928 100644
--- a/commons-jcs-core/src/test/conf/cache.ccf
+++ b/commons-jcs-core/src/test/conf/cache.ccf
@@ -18,13 +18,13 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=DC
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=200001
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=true
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLife=700
 jcs.default.elementattributes.IdleTime=1800
diff --git a/commons-jcs-core/src/test/conf/cache2.ccf b/commons-jcs-core/src/test/conf/cache2.ccf
index 83ed80c..3aeec13 100644
--- a/commons-jcs-core/src/test/conf/cache2.ccf
+++ b/commons-jcs-core/src/test/conf/cache2.ccf
@@ -18,59 +18,59 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=DC
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=200000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 # should be defined for the storage of group attribute list
 jcs.system.groupIdCache=DC
-jcs.system.groupIdCache.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.system.groupIdCache.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.system.groupIdCache.cacheattributes.MaxObjects=1000
-jcs.system.groupIdCache.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.system.groupIdCache.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 # #############################################################
 # ################# CACHE REGIONS AVAILABLE ###################
 jcs.region.testCache1=DC,RC
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=10
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache1.cacheattributes.UseMemoryShrinker=true
 jcs.region.testCache1.cacheattributes.ShrinkerIntervalSeconds=30
 jcs.region.testCache1.cacheattributes.MaxMemoryIdleTimeSeconds=300
 jcs.region.testCache1.cacheattributes.MaxSpoolPerRun=100
-jcs.region.testCache1.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache1.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache1.elementattributes.IsEternal=false
 jcs.region.testCache1.elementattributes.MaxLifeSeconds=60000
 jcs.region.testCache1.elementattributes.IsLateral=true
 jcs.region.testCache1.elementattributes.IsRemote=true
 
 jcs.region.testCache2=DC
-jcs.region.testCache2.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache2.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache2.cacheattributes.MaxObjects=1000
-jcs.region.testCache2.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache2.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 # prefered config
 jcs.region.test2=DC
-jcs.region.test2.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.test2.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.test2.cacheattributes.MaxObjects=1000
 
 
 # #############################################################
 # ################# AUXILIARY CACHES AVAILABLE ################
-jcs.auxiliary.HC=org.apache.commons.jcs.auxiliary.disk.hsql.HSQLCacheFactory
-jcs.auxiliary.HC.attributes=org.apache.commons.jcs.auxiliary.disk.hsql.HSQLCacheAttributes
+jcs.auxiliary.HC=org.apache.commons.jcs3.auxiliary.disk.hsql.HSQLCacheFactory
+jcs.auxiliary.HC.attributes=org.apache.commons.jcs3.auxiliary.disk.hsql.HSQLCacheAttributes
 jcs.auxiliary.HC.attributes.DiskPath=@project_home@/hsql
 
 # standard disk cache
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=target/test-sandbox
 
 # need to make put or invalidate an option
 # just a remove lock to add
-jcs.auxiliary.RC=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RC.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RC=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RC.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RC.attributes.FailoverServers=localhost:1101
 jcs.auxiliary.RC.attributes.LocalPort=1202
 jcs.auxiliary.RC.attributes.RemoveUponRemotePut=false
@@ -79,45 +79,45 @@
 jcs.auxiliary.RC.attributes.GetOnly=false
 
 # unreliable
-jcs.auxiliary.LUDP=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LUDP.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LUDP=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LUDP.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LUDP.attributes.TransmissionTypeName=UDP
 jcs.auxiliary.LUDP.attributes.UdpMulticastAddr=228.5.6.7
 jcs.auxiliary.LUDP.attributes.UdpMulticastPort=6789
 
-jcs.auxiliary.LJG=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LJG.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LJG=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LJG.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LJG.attributes.TransmissionTypeName=JAVAGROUPS
 jcs.auxiliary.LJG.attributes.UdpMulticastAddr=228.5.6.7
 jcs.auxiliary.LJG.attributes.UdpMulticastPort=6789
 jcs.auxiliary.LJG.attributes.PutOnlyMode=true
 
 # almost complete
-jcs.auxiliary.LTCP=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LTCP.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LTCP=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LTCP.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LTCP.attributes.TransmissionTypeName=TCP
 jcs.auxiliary.LTCP.attributes.TcpServers=localhost:1111
 jcs.auxiliary.LTCP.attributes.TcpListenerPort=1112
 jcs.auxiliary.LTCP.attributes.PutOnlyMode=true
 
-jcs.auxiliary.XMLRPC=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.XMLRPC.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.XMLRPC=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.XMLRPC.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.XMLRPC.attributes.TransmissionTypeName=XMLRPC
 jcs.auxiliary.XMLRPC.attributes.HttpServers=localhost:8181
 jcs.auxiliary.XMLRPC.attributes.HttpListenerPort=8182
 jcs.auxiliary.XMLRPC.attributes.PutOnlyMode=false
 
 
-jcs.auxiliary.LTCP2=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LTCP2.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LTCP2=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LTCP2.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LTCP2.attributes.TransmissionTypeName=TCP
 jcs.auxiliary.LTCP2.attributes.TcpServers=localhost:1111,localhost2:1112
 jcs.auxiliary.LTCP2.attributes.TcpListenerPort=1111
 
 # example of how to configure the http version of the lateral cache
 # not converteed to new cache
-jcs.auxiliary.LCHTTP=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LCHTTP.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LCHTTP=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LCHTTP.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LCHTTP.attributes.TransmissionType=HTTP
 jcs.auxiliary.LCHTTP.attributes.httpServers=localhost:8080,localhost:7001,localhost:80
 jcs.auxiliary.LCHTTP.attributes.httpReceiveServlet=/cache/LateralCacheReceiverServlet
diff --git a/commons-jcs-core/src/test/conf/cache3.ccf b/commons-jcs-core/src/test/conf/cache3.ccf
index 54e4962..412f80c 100644
--- a/commons-jcs-core/src/test/conf/cache3.ccf
+++ b/commons-jcs-core/src/test/conf/cache3.ccf
@@ -18,51 +18,51 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=DC
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=1000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 # should be defined for the storage of group attribute list
 jcs.system.groupIdCache=DC
-jcs.system.groupIdCache.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.system.groupIdCache.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.system.groupIdCache.cacheattributes.MaxObjects=1000
-jcs.system.groupIdCache.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.system.groupIdCache.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 # #############################################################
 # ################# CACHE REGIONS AVAILABLE ###################
 jcs.region.testCache1=RC
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=1000
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache1.elementattributes.IsLateral=true
 
 jcs.region.testCache2=DC
-jcs.region.testCache2.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache2.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache2.cacheattributes.MaxObjects=1000
-jcs.region.testCache2.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache2.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 # prefered config
 jcs.region.test2=DC
-jcs.region.test2.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.test2.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.test2.cacheattributes.MaxObjects=1000
 
 
 # #############################################################
 # ################# AUXILIARY CACHES AVAILABLE ################
-jcs.auxiliary.HC=org.apache.commons.jcs.auxiliary.disk.hsql.HSQLCacheFactory
-jcs.auxiliary.HC.attributes=org.apache.commons.jcs.auxiliary.disk.hsql.HSQLCacheAttributes
+jcs.auxiliary.HC=org.apache.commons.jcs3.auxiliary.disk.hsql.HSQLCacheFactory
+jcs.auxiliary.HC.attributes=org.apache.commons.jcs3.auxiliary.disk.hsql.HSQLCacheAttributes
 jcs.auxiliary.HC.attributes.DiskPath=@project_home@/hsql
 
 # standard disk cache
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=@project_home@/raf
 
 # need to make put or invalidate an option
 # just a remove lock to add
-jcs.auxiliary.RC=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RC.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RC=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RC.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RC.attributes.RemoteHost=10.21.209.150
 jcs.auxiliary.RC.attributes.RemotePort=1102
 # jcs.auxiliary.RC.attributes.LocalPort=1103
@@ -71,45 +71,45 @@
 
 
 # unreliable
-jcs.auxiliary.LUDP=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LUDP.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LUDP=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LUDP.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LUDP.attributes.TransmissionTypeName=UDP
 jcs.auxiliary.LUDP.attributes.UdpMulticastAddr=228.5.6.7
 jcs.auxiliary.LUDP.attributes.UdpMulticastPort=6789
 
-jcs.auxiliary.LJG=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LJG.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LJG=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LJG.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LJG.attributes.TransmissionTypeName=JAVAGROUPS
 jcs.auxiliary.LJG.attributes.UdpMulticastAddr=228.5.6.7
 jcs.auxiliary.LJG.attributes.UdpMulticastPort=6789
 jcs.auxiliary.LJG.attributes.PutOnlyMode=true
 
 # almost complete
-jcs.auxiliary.LTCP=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LTCP.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LTCP=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LTCP.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LTCP.attributes.TransmissionTypeName=TCP
 jcs.auxiliary.LTCP.attributes.TcpServers=localhost:1111
 jcs.auxiliary.LTCP.attributes.TcpListenerPort=1112
 jcs.auxiliary.LTCP.attributes.PutOnlyMode=false
 
-jcs.auxiliary.XMLRPC=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.XMLRPC.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.XMLRPC=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.XMLRPC.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.XMLRPC.attributes.TransmissionTypeName=XMLRPC
 jcs.auxiliary.XMLRPC.attributes.HttpServers=localhost:8181
 jcs.auxiliary.XMLRPC.attributes.HttpListenerPort=8182
 jcs.auxiliary.XMLRPC.attributes.PutOnlyMode=false
 
 
-jcs.auxiliary.LTCP2=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LTCP2.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LTCP2=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LTCP2.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LTCP2.attributes.TransmissionTypeName=TCP
 jcs.auxiliary.LTCP2.attributes.TcpServers=localhost:1111,localhost2:1112
 jcs.auxiliary.LTCP2.attributes.TcpListenerPort=1111
 
 # example of how to configure the http version of the lateral cache
 # not converteed to new cache
-jcs.auxiliary.LCHTTP=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LCHTTP.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LCHTTP=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LCHTTP.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LCHTTP.attributes.TransmissionType=HTTP
 jcs.auxiliary.LCHTTP.attributes.httpServers=localhost:8080,localhost:7001,localhost:80
 jcs.auxiliary.LCHTTP.attributes.httpReceiveServlet=/cache/LateralCacheReceiverServlet
diff --git a/commons-jcs-core/src/test/conf/cacheB.ccf b/commons-jcs-core/src/test/conf/cacheB.ccf
index 4127e9b..835f439 100644
--- a/commons-jcs-core/src/test/conf/cacheB.ccf
+++ b/commons-jcs-core/src/test/conf/cacheB.ccf
@@ -18,13 +18,13 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=DC
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=1000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=true
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLife=700
 jcs.default.elementattributes.IdleTime=1800
@@ -37,27 +37,27 @@
 # ################# CACHE REGIONS AVAILABLE ###################
 # Regions preconfigured for caching
 jcs.region.testCache1=RC
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=1000
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache1.cacheattributes.UseMemoryShrinker=true
 jcs.region.testCache1.cacheattributes.ShrinkerIntervalSeconds=30
 jcs.region.testCache1.cacheattributes.MaxMemoryIdleTimeSeconds=300
 jcs.region.testCache1.cacheattributes.MaxSpoolPerRun=100
-jcs.region.testCache1.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache1.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache1.elementattributes.IsEternal=false
 jcs.region.testCache1.elementattributes.MaxLifeSeconds=60000
 jcs.region.testCache1.elementattributes.IsLateral=true
 jcs.region.testCache1.elementattributes.IsRemote=true
 
 jcs.region.testCache2=DC
-jcs.region.testCache2.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache2.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache2.cacheattributes.MaxObjects=100
-jcs.region.testCache2.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache2.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache2.cacheattributes.UseMemoryShrinker=true
 jcs.region.testCache2.cacheattributes.MaxMemoryIdleTimeSeconds=1000
 jcs.region.testCache2.cacheattributes.ShrinkerIntervalSeconds=40
-jcs.region.testCache2.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache2.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache2.elementattributes.IsEternal=false
 jcs.region.testCache2.elementattributes.MaxLifeSeconds=600
 jcs.region.testCache2.elementattributes.IsSpool=true
@@ -65,13 +65,13 @@
 jcs.region.testCache2.elementattributes.IsLateral=true
 
 jcs.region.testCache3=
-jcs.region.testCache3.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache3.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache3.cacheattributes.MaxObjects=100000
-jcs.region.testCache3.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache3.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache3.cacheattributes.UseMemoryShrinker=false
 jcs.region.testCache3.cacheattributes.MaxMemoryIdleTimeSeconds=10
 jcs.region.testCache3.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.region.testCache3.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache3.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache3.elementattributes.IsEternal=false
 jcs.region.testCache3.elementattributes.MaxLifeSeconds=3600
 jcs.region.testCache3.elementattributes.IsSpool=true
@@ -83,23 +83,23 @@
 # ################# AUXILIARY CACHES AVAILABLE ################
 
 # Remote RMI cache without failover
-jcs.auxiliary.RGroup=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RGroup.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RGroup=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RGroup.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RGroup.attributes.RemoteTypeName=LOCAL
 jcs.auxiliary.RGroup.attributes.RemoteHost=localhost
 jcs.auxiliary.RGroup.attributes.RemotePort=1102
 jcs.auxiliary.RGroup.attributes.GetOnly=true
 
 # Remote RMI Cache set up to failover
-jcs.auxiliary.RFailover=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RFailover.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RFailover=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RFailover.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RFailover.attributes.RemoteTypeName=LOCAL
 jcs.auxiliary.RFailover.attributes.FailoverServers=localhost:1102
 jcs.auxiliary.RFailover.attributes.GetOnly=false
 
 # Primary Disk Cache-- faster than the rest because of memory key storage
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=target/test-sandbox/raf
 jcs.auxiliary.DC.attributes.MaxPurgatorySize=10000
 jcs.auxiliary.DC.attributes.MaxKeySize=10000
@@ -112,8 +112,8 @@
 # If you want to use a separate pool for each disk cache, either use
 # the single model or define a different auxiliary for each region and use the Pooled.
 # SINGLE is best unless you have a huge # of regions.
-jcs.auxiliary.DC2=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC2.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC2=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC2.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC2.attributes.DiskPath=target/test-sandbox/raf
 jcs.auxiliary.DC2.attributes.MaxPurgatorySize=10000
 jcs.auxiliary.DC2.attributes.MaxKeySize=10000
@@ -122,28 +122,28 @@
 jcs.auxiliary.DC2.attributes.EventQueuePoolName=disk_cache_event_queue
 
 # Berkeley DB JE
-jcs.auxiliary.JE=org.apache.commons.jcs.auxiliary.disk.bdbje.BDBJECacheFactory
-jcs.auxiliary.JE.attributes=org.apache.commons.jcs.auxiliary.disk.bdbje.BDBJECacheAttributes
+jcs.auxiliary.JE=org.apache.commons.jcs3.auxiliary.disk.bdbje.BDBJECacheFactory
+jcs.auxiliary.JE.attributes=org.apache.commons.jcs3.auxiliary.disk.bdbje.BDBJECacheAttributes
 jcs.auxiliary.JE.attributes.DiskPath=target/test-sandbox/bdbje-disk-cache-conc
 # the minimum cache size is 1024
 jcs.auxiliary.indexedDiskCache.attributes.CacheSize=1024
 # jcs.auxiliary.indexedDiskCache.attributes.CachePercent=0
 
 # HSQL Disk Cache -- too slow as is
-jcs.auxiliary.HDC=org.apache.commons.jcs.auxiliary.disk.hsql.HSQLCacheFactory
-jcs.auxiliary.HDC.attributes=org.apache.commons.jcs.auxiliary.disk.hsql.HSQLCacheAttributes
+jcs.auxiliary.HDC=org.apache.commons.jcs3.auxiliary.disk.hsql.HSQLCacheFactory
+jcs.auxiliary.HDC.attributes=org.apache.commons.jcs3.auxiliary.disk.hsql.HSQLCacheAttributes
 jcs.auxiliary.HDC.attributes.DiskPath=@project_home_f@hsql
 
 # JISP Disk Cache -- save memory with disk key storage
-jcs.auxiliary.JDC=org.apache.commons.jcs.auxiliary.disk.jisp.JISPCacheFactory
-jcs.auxiliary.JDC.attributes=org.apache.commons.jcs.auxiliary.disk.jisp.JISPCacheAttributes
+jcs.auxiliary.JDC=org.apache.commons.jcs3.auxiliary.disk.jisp.JISPCacheFactory
+jcs.auxiliary.JDC.attributes=org.apache.commons.jcs3.auxiliary.disk.jisp.JISPCacheAttributes
 jcs.auxiliary.JDC.attributes.DiskPath=@project_home_f@raf
 jcs.auxiliary.JDC.attributes.ClearOnStart=false
 
 # need to make put or invalidate an option
 # just a remove lock to add
-jcs.auxiliary.RC=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RC.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RC=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RC.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RC.attributes.FailoverServers=localhost:1101,localhost:1102,localhost:1103
 jcs.auxiliary.RC.attributes.LocalPort=1204
 jcs.auxiliary.RC.attributes.RemoveUponRemotePut=false
@@ -152,42 +152,42 @@
 jcs.auxiliary.RC.attributes.GetOnly=false
 
 # unreliable
-jcs.auxiliary.LUDP=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LUDP.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LUDP=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LUDP.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LUDP.attributes.TransmissionTypeName=UDP
 jcs.auxiliary.LUDP.attributes.UdpMulticastAddr=228.5.6.7
 jcs.auxiliary.LUDP.attributes.UdpMulticastPort=6789
 
-jcs.auxiliary.LJG=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LJG.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LJG=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LJG.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LJG.attributes.TransmissionTypeName=JAVAGROUPS
 jcs.auxiliary.LJG.attributes.PutOnlyMode=true
 jcs.auxiliary.LJG.attributes.JGChannelProperties = UDP(mcast_addr=224.0.0.100;mcast_port=751):PING(timeout=3000):FD:STABLE:NAKACK:UNICAST:FRAG:FLUSH:GMS:VIEW_ENFORCER:QUEUE
 
 
-jcs.auxiliary.JG = org.apache.commons.jcs.auxiliary.javagroups.JavaGroupsCacheFactory
-jcs.auxiliary.JG.attributes = org.apache.commons.jcs.auxiliary.javagroups.JavaGroupsCacheAttributes
+jcs.auxiliary.JG = org.apache.commons.jcs3.auxiliary.javagroups.JavaGroupsCacheFactory
+jcs.auxiliary.JG.attributes = org.apache.commons.jcs3.auxiliary.javagroups.JavaGroupsCacheAttributes
 jcs.auxiliary.JG.attributes.ChannelFactoryClassName = org.javagroups.JChannelFactory
 jcs.auxiliary.JG.attributes.ChannelProperties = UDP(mcast_addr=224.0.0.100;mcast_port=7501):PING:FD:STABLE:NAKACK:UNICAST:FRAG:FLUSH:GMS:VIEW_ENFORCER:QUEUE
 
 
 # almost complete
-jcs.auxiliary.LTCP=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LTCP.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LTCP=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LTCP.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LTCP.attributes.TransmissionTypeName=TCP
 jcs.auxiliary.LTCP.attributes.TcpServers=localhost:1112
 jcs.auxiliary.LTCP.attributes.TcpListenerPort=1111
 jcs.auxiliary.LTCP.attributes.PutOnlyMode=false
 
-jcs.auxiliary.LTCP2=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LTCP2.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LTCP2=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LTCP2.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LTCP2.attributes.TransmissionTypeName=TCP
 jcs.auxiliary.LTCP2.attributes.TcpServers=localhost:1112
 jcs.auxiliary.LTCP2.attributes.TcpListenerPort=1111
 jcs.auxiliary.LTCP2.attributes.PutOnlyMode=true
 
-jcs.auxiliary.XMLRPC=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.XMLRPC.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.XMLRPC=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.XMLRPC.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.XMLRPC.attributes.TransmissionTypeName=XMLRPC
 jcs.auxiliary.XMLRPC.attributes.HttpServers=localhost:8182
 jcs.auxiliary.XMLRPC.attributes.HttpListenerPort=8181
@@ -196,8 +196,8 @@
 
 # example of how to configure the http version of the lateral cache
 # not converteed to new cache
-jcs.auxiliary.LCHTTP=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LCHTTP.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LCHTTP=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LCHTTP.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LCHTTP.attributes.TransmissionType=HTTP
 jcs.auxiliary.LCHTTP.attributes.httpServers=localhost:8080,localhost:7001,localhost:80
 jcs.auxiliary.LCHTTP.attributes.httpReceiveServlet=/cache/LateralCacheReceiverServlet
diff --git a/commons-jcs-core/src/test/conf/cacheBDB.ccf b/commons-jcs-core/src/test/conf/cacheBDB.ccf
index 90178aa..b05eb5b 100644
--- a/commons-jcs-core/src/test/conf/cacheBDB.ccf
+++ b/commons-jcs-core/src/test/conf/cacheBDB.ccf
@@ -18,38 +18,38 @@
 # a maximum of 100 objects, so objects should get pushed into the disk cache
 
 jcs.default=bdbje
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=100
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 # #### CACHE REGIONS FOR TEST
 
 jcs.region.indexedRegion1=bdbje
-jcs.region.indexedRegion1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.indexedRegion1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.indexedRegion1.cacheattributes.MaxObjects=100
-jcs.region.indexedRegion1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.indexedRegion1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 jcs.region.indexedRegion2=bdbje
-jcs.region.indexedRegion2.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.indexedRegion2.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.indexedRegion2.cacheattributes.MaxObjects=100
-jcs.region.indexedRegion2.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.indexedRegion2.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 jcs.region.indexedRegion3=bdbje
-jcs.region.indexedRegion3.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.indexedRegion3.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.indexedRegion3.cacheattributes.MaxObjects=100
-jcs.region.indexedRegion3.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.indexedRegion3.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 jcs.region.indexedRegion4=bdbje
-jcs.region.indexedRegion4.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.indexedRegion4.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.indexedRegion4.cacheattributes.MaxObjects=100
-jcs.region.indexedRegion4.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.indexedRegion4.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.indexedRegion4.cacheattributes.UseMemoryShrinker=false
 
 
 # #### AUXILIARY CACHES
 
 # Berkeley DB JE
-jcs.auxiliary.bdbje=org.apache.commons.jcs.auxiliary.disk.bdbje.BDBJECacheFactory
-jcs.auxiliary.bdbje.attributes=org.apache.commons.jcs.auxiliary.disk.bdbje.BDBJECacheAttributes
+jcs.auxiliary.bdbje=org.apache.commons.jcs3.auxiliary.disk.bdbje.BDBJECacheFactory
+jcs.auxiliary.bdbje.attributes=org.apache.commons.jcs3.auxiliary.disk.bdbje.BDBJECacheAttributes
 jcs.auxiliary.bdbje.attributes.DiskPath=target/
 jcs.auxiliary.bdbje.attributes.MaxPurgatorySize=100000
diff --git a/commons-jcs-core/src/test/conf/cacheD10A.ccf b/commons-jcs-core/src/test/conf/cacheD10A.ccf
index 8e1969f..a797c80 100644
--- a/commons-jcs-core/src/test/conf/cacheD10A.ccf
+++ b/commons-jcs-core/src/test/conf/cacheD10A.ccf
@@ -18,13 +18,13 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=DC,RC
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=200001
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=true
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLife=700
 jcs.default.elementattributes.IdleTime=1800
@@ -37,27 +37,27 @@
 # ################# CACHE REGIONS AVAILABLE ###################
 # Regions preconfigured for caching
 jcs.region.testCache1=DC,RC
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=1000000
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache1.cacheattributes.UseMemoryShrinker=true
 jcs.region.testCache1.cacheattributes.ShrinkerIntervalSeconds=30
 jcs.region.testCache1.cacheattributes.MaxMemoryIdleTimeSeconds=300
 jcs.region.testCache1.cacheattributes.MaxSpoolPerRun=100
-jcs.region.testCache1.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache1.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache1.elementattributes.IsEternal=false
 jcs.region.testCache1.elementattributes.MaxLifeSeconds=60000
 jcs.region.testCache1.elementattributes.IsLateral=true
 jcs.region.testCache1.elementattributes.IsRemote=true
 
 jcs.region.testCache2=DC,RC
-jcs.region.testCache2.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache2.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache2.cacheattributes.MaxObjects=100
-jcs.region.testCache2.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache2.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache2.cacheattributes.UseMemoryShrinker=true
 jcs.region.testCache2.cacheattributes.MaxMemoryIdleTimeSeconds=1000
 jcs.region.testCache2.cacheattributes.ShrinkerIntervalSeconds=40
-jcs.region.testCache2.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache2.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache2.elementattributes.IsEternal=false
 jcs.region.testCache2.elementattributes.MaxLifeSeconds=600
 jcs.region.testCache2.elementattributes.IsSpool=true
@@ -65,13 +65,13 @@
 jcs.region.testCache2.elementattributes.IsLateral=true
 
 jcs.region.testCache3=
-jcs.region.testCache3.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache3.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache3.cacheattributes.MaxObjects=100000
-jcs.region.testCache3.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache3.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache3.cacheattributes.UseMemoryShrinker=false
 jcs.region.testCache3.cacheattributes.MaxMemoryIdleTimeSeconds=10
 jcs.region.testCache3.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.region.testCache3.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache3.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache3.elementattributes.IsEternal=false
 jcs.region.testCache3.elementattributes.MaxLifeSeconds=3600
 jcs.region.testCache3.elementattributes.IsSpool=true
@@ -83,8 +83,8 @@
 # ################# AUXILIARY CACHES AVAILABLE ################
 
 # Primary Disk Cache-- faster than the rest because of memory key storage
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=target/test-sandbox/raf
 jcs.auxiliary.DC.attributes.MaxPurgatorySize=10000000
 jcs.auxiliary.DC.attributes.MaxKeySize=1000000
@@ -98,8 +98,8 @@
 # If you want to use a separate pool for each disk cache, either use
 # the single model or define a different auxiliary for each region and use the Pooled.
 # SINGLE is best unless you have a huge # of regions.
-jcs.auxiliary.DC2=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC2.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC2=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC2.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC2.attributes.DiskPath=target/test-sandbox/raf
 jcs.auxiliary.DC2.attributes.MaxPurgatorySize=10000
 jcs.auxiliary.DC2.attributes.MaxKeySize=10000
@@ -110,8 +110,8 @@
 
 # need to make put or invalidate an option
 # just a remove lock to add
-jcs.auxiliary.RC=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RC.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RC=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RC.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RC.attributes.FailoverServers=dev10.hq.site59.com:1101
 jcs.auxiliary.RC.attributes.LocalPort=1200
 jcs.auxiliary.RC.attributes.RemoveUponRemotePut=false
@@ -122,8 +122,8 @@
 # jcs.auxiliary.RC.attributes.GetOnly=false
 
 # Remote RMI Cache set up to failover
-jcs.auxiliary.RFailover=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RFailover.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RFailover=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RFailover.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RFailover.attributes.FailoverServers=dev10.hq.site59.com:1101
 jcs.auxiliary.RC.attributes.RemoveUponRemotePut=true
 jcs.auxiliary.RFailover.attributes.GetOnly=false
diff --git a/commons-jcs-core/src/test/conf/cacheD10B.ccf b/commons-jcs-core/src/test/conf/cacheD10B.ccf
index 020ebce..f5103f3 100644
--- a/commons-jcs-core/src/test/conf/cacheD10B.ccf
+++ b/commons-jcs-core/src/test/conf/cacheD10B.ccf
@@ -18,13 +18,13 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=DC,RC
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=200001
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=true
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLife=700
 jcs.default.elementattributes.IdleTime=1800
@@ -37,27 +37,27 @@
 # ################# CACHE REGIONS AVAILABLE ###################
 # Regions preconfigured for caching
 jcs.region.testCache1=DC,RC
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=1000000
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache1.cacheattributes.UseMemoryShrinker=true
 jcs.region.testCache1.cacheattributes.ShrinkerIntervalSeconds=30
 jcs.region.testCache1.cacheattributes.MaxMemoryIdleTimeSeconds=300
 jcs.region.testCache1.cacheattributes.MaxSpoolPerRun=100
-jcs.region.testCache1.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache1.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache1.elementattributes.IsEternal=false
 jcs.region.testCache1.elementattributes.MaxLifeSeconds=60000
 jcs.region.testCache1.elementattributes.IsLateral=true
 jcs.region.testCache1.elementattributes.IsRemote=true
 
 jcs.region.testCache2=DC,RC
-jcs.region.testCache2.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache2.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache2.cacheattributes.MaxObjects=100
-jcs.region.testCache2.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache2.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache2.cacheattributes.UseMemoryShrinker=true
 jcs.region.testCache2.cacheattributes.MaxMemoryIdleTimeSeconds=1000
 jcs.region.testCache2.cacheattributes.ShrinkerIntervalSeconds=40
-jcs.region.testCache2.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache2.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache2.elementattributes.IsEternal=false
 jcs.region.testCache2.elementattributes.MaxLifeSeconds=600
 jcs.region.testCache2.elementattributes.IsSpool=true
@@ -65,13 +65,13 @@
 jcs.region.testCache2.elementattributes.IsLateral=true
 
 jcs.region.testCache3=
-jcs.region.testCache3.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache3.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache3.cacheattributes.MaxObjects=100000
-jcs.region.testCache3.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache3.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache3.cacheattributes.UseMemoryShrinker=false
 jcs.region.testCache3.cacheattributes.MaxMemoryIdleTimeSeconds=10
 jcs.region.testCache3.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.region.testCache3.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache3.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache3.elementattributes.IsEternal=false
 jcs.region.testCache3.elementattributes.MaxLifeSeconds=3600
 jcs.region.testCache3.elementattributes.IsSpool=true
@@ -83,8 +83,8 @@
 # ################# AUXILIARY CACHES AVAILABLE ################
 
 # Primary Disk Cache-- faster than the rest because of memory key storage
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=target/test-sandbox/raf
 jcs.auxiliary.DC.attributes.MaxPurgatorySize=10000000
 jcs.auxiliary.DC.attributes.MaxKeySize=1000000
@@ -98,8 +98,8 @@
 # If you want to use a separate pool for each disk cache, either use
 # the single model or define a different auxiliary for each region and use the Pooled.
 # SINGLE is best unless you have a huge # of regions.
-jcs.auxiliary.DC2=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC2.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC2=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC2.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC2.attributes.DiskPath=target/test-sandbox/raf
 jcs.auxiliary.DC2.attributes.MaxPurgatorySize=10000
 jcs.auxiliary.DC2.attributes.MaxKeySize=10000
@@ -110,8 +110,8 @@
 
 # need to make put or invalidate an option
 # just a remove lock to add
-jcs.auxiliary.RC=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RC.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RC=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RC.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RC.attributes.FailoverServers=dev10.hq.site59.com:1101
 jcs.auxiliary.RC.attributes.LocalPort=1201
 jcs.auxiliary.RC.attributes.RemoveUponRemotePut=false
@@ -122,8 +122,8 @@
 # jcs.auxiliary.RC.attributes.GetOnly=false
 
 # Remote RMI Cache set up to failover
-jcs.auxiliary.RFailover=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RFailover.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RFailover=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RFailover.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RFailover.attributes.FailoverServers=dev10.hq.site59.com:1101
 jcs.auxiliary.RC.attributes.RemoveUponRemotePut=true
 jcs.auxiliary.RFailover.attributes.GetOnly=false
diff --git a/commons-jcs-core/src/test/conf/cacheID.ccf b/commons-jcs-core/src/test/conf/cacheID.ccf
index 5cd50e1..755daa0 100644
--- a/commons-jcs-core/src/test/conf/cacheID.ccf
+++ b/commons-jcs-core/src/test/conf/cacheID.ccf
@@ -18,13 +18,13 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=DC
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=0
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=true
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLife=700
 jcs.default.elementattributes.IdleTime=1800
@@ -37,14 +37,14 @@
 # ################# CACHE REGIONS AVAILABLE ###################
 # Regions preconfigured for caching
 jcs.region.testCache1=DC
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=0
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache1.cacheattributes.UseMemoryShrinker=true
 jcs.region.testCache1.cacheattributes.ShrinkerIntervalSeconds=30
 jcs.region.testCache1.cacheattributes.MaxMemoryIdleTimeSeconds=300
 jcs.region.testCache1.cacheattributes.MaxSpoolPerRun=100
-jcs.region.testCache1.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache1.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache1.elementattributes.IsEternal=false
 jcs.region.testCache1.elementattributes.MaxLifeSeconds=60000
 jcs.region.testCache1.elementattributes.IsLateral=true
@@ -55,8 +55,8 @@
 # ################# AUXILIARY CACHES AVAILABLE ################
 
 # Primary Disk Cache-- faster than the rest because of memory key storage
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=target/test-sandbox/raf
 jcs.auxiliary.DC.attributes.MaxPurgatorySize=10000000
 jcs.auxiliary.DC.attributes.MaxKeySize=1000000
diff --git a/commons-jcs-core/src/test/conf/cacheJG1.ccf b/commons-jcs-core/src/test/conf/cacheJG1.ccf
index d6f7baf..1d9681c 100644
--- a/commons-jcs-core/src/test/conf/cacheJG1.ccf
+++ b/commons-jcs-core/src/test/conf/cacheJG1.ccf
@@ -18,13 +18,13 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=LJG
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=250000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=false
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLifeSeconds=700
 jcs.default.elementattributes.IdleTime=1800
@@ -37,14 +37,14 @@
 # ################# CACHE REGIONS AVAILABLE ###################
 # Regions preconfigured for caching
 jcs.region.testCache1=LJG
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=250000
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache1.cacheattributes.UseMemoryShrinker=false
 jcs.region.testCache1.cacheattributes.ShrinkerIntervalSeconds=30
 jcs.region.testCache1.cacheattributes.MaxMemoryIdleTimeSeconds=300
 jcs.region.testCache1.cacheattributes.MaxSpoolPerRun=100
-jcs.region.testCache1.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache1.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache1.elementattributes.IsEternal=false
 jcs.region.testCache1.elementattributes.MaxLifeSeconds=60000
 jcs.region.testCache1.elementattributes.IsLateral=true
@@ -55,16 +55,16 @@
 # #############################################################
 # ################# AUXILIARY CACHES AVAILABLE ################
 # Primary Disk Cache-- faster than the rest because of memory key storage
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=logs/rafJG1
 jcs.auxiliary.DC.attributes.MaxPurgatorySize=100000
 jcs.auxiliary.DC.attributes.MaxKeySize=500000
 jcs.auxiliary.DC.attributes.OptimizeAtRemoveCount=-1
 
 # Lateral JavaGroups Distribution
-jcs.auxiliary.LJG=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LJG.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LJG=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LJG.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LJG.attributes.TransmissionTypeName=JAVAGROUPS
 jcs.auxiliary.LJG.attributes.PutOnlyMode=true
 jcs.auxiliary.LJG.attributes.JGChannelProperties=UDP(mcast_addr=224.0.0.100;mcast_port=7501):PING:FD:STABLE:NAKACK:UNICAST:FRAG:FLUSH:GMS:QUEUE
@@ -74,8 +74,8 @@
 jcs.auxiliary.LJG.attributes.EventQueueType=POOLED
 jcs.auxiliary.LJG.attributes.EventQueuePolName=lg_event_queue
 
-jcs.auxiliary.JG = org.apache.commons.jcs.auxiliary.javagroups.JavaGroupsCacheFactory
-jcs.auxiliary.JG.attributes = org.apache.commons.jcs.auxiliary.javagroups.JavaGroupsCacheAttributes
+jcs.auxiliary.JG = org.apache.commons.jcs3.auxiliary.javagroups.JavaGroupsCacheFactory
+jcs.auxiliary.JG.attributes = org.apache.commons.jcs3.auxiliary.javagroups.JavaGroupsCacheAttributes
 jcs.auxiliary.JG.attributes.ChannelFactoryClassName = org.jgroups.JChannelFactory
 jcs.auxiliary.JG.attributes.ChannelProperties = UDP(mcast_addr=224.0.0.100;mcast_port=7501):PING:FD:STABLE:NAKACK:UNICAST:FRAG:FLUSH:GMS:VIEW_ENFORCER:QUEUE
 
diff --git a/commons-jcs-core/src/test/conf/cacheJG2.ccf b/commons-jcs-core/src/test/conf/cacheJG2.ccf
index 8409ac5..ef2838f 100644
--- a/commons-jcs-core/src/test/conf/cacheJG2.ccf
+++ b/commons-jcs-core/src/test/conf/cacheJG2.ccf
@@ -18,13 +18,13 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=LJG
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=250000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=false
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLifeSeconds=700
 jcs.default.elementattributes.IdleTime=1800
@@ -37,14 +37,14 @@
 # ################# CACHE REGIONS AVAILABLE ###################
 # Regions preconfigured for caching
 jcs.region.testCache1=LJG
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=250000
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache1.cacheattributes.UseMemoryShrinker=false
 jcs.region.testCache1.cacheattributes.ShrinkerIntervalSeconds=30
 jcs.region.testCache1.cacheattributes.MaxMemoryIdleTimeSeconds=300
 jcs.region.testCache1.cacheattributes.MaxSpoolPerRun=100
-jcs.region.testCache1.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache1.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache1.elementattributes.IsEternal=false
 jcs.region.testCache1.elementattributes.MaxLifeSeconds=60000
 jcs.region.testCache1.elementattributes.IsLateral=true
@@ -55,16 +55,16 @@
 # #############################################################
 # ################# AUXILIARY CACHES AVAILABLE ################
 # Primary Disk Cache-- faster than the rest because of memory key storage
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=logs/rafJG2
 jcs.auxiliary.DC.attributes.MaxPurgatorySize=100000
 jcs.auxiliary.DC.attributes.MaxKeySize=500000
 jcs.auxiliary.DC.attributes.OptimizeAtRemoveCount=-1
 
 # Lateral JavaGroups Distribution
-jcs.auxiliary.LJG=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LJG.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LJG=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LJG.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LJG.attributes.TransmissionTypeName=JAVAGROUPS
 jcs.auxiliary.LJG.attributes.PutOnlyMode=true
 jcs.auxiliary.LJG.attributes.JGChannelProperties=UDP(mcast_addr=224.0.0.100;mcast_port=7501):PING:FD:STABLE:NAKACK:UNICAST:FRAG:FLUSH:GMS:QUEUE
@@ -74,7 +74,7 @@
 # UDP(mcast_addr=225.0.0.100;mcast_port=7777):PING(timeout=3000):AUTOCONF:FD:STABLE:NAKACK:UNICAST:FRAG:FLUSH:GMS:STATE_TRANSFER:QUEUE
 # UDP(mcast_addr=225.0.0.100;mcast_port=7777):PING(timeout=3000):pbcast.FD:pbcast.NAKACK:pbcast.STABLE:UNICAST:pbcast.GMS:FRAG
 
-jcs.auxiliary.JG = org.apache.commons.jcs.auxiliary.javagroups.JavaGroupsCacheFactory
-jcs.auxiliary.JG.attributes = org.apache.commons.jcs.auxiliary.javagroups.JavaGroupsCacheAttributes
+jcs.auxiliary.JG = org.apache.commons.jcs3.auxiliary.javagroups.JavaGroupsCacheFactory
+jcs.auxiliary.JG.attributes = org.apache.commons.jcs3.auxiliary.javagroups.JavaGroupsCacheAttributes
 jcs.auxiliary.JG.attributes.ChannelFactoryClassName = org.jgroups.JChannelFactory
 jcs.auxiliary.JG.attributes.ChannelProperties = UDP(mcast_addr=224.0.0.100;mcast_port=7501):PING:FD:STABLE:NAKACK:UNICAST:FRAG:FLUSH:GMS:VIEW_ENFORCER:QUEUE
diff --git a/commons-jcs-core/src/test/conf/cacheJG3.ccf b/commons-jcs-core/src/test/conf/cacheJG3.ccf
index 806ae26..c304617 100644
--- a/commons-jcs-core/src/test/conf/cacheJG3.ccf
+++ b/commons-jcs-core/src/test/conf/cacheJG3.ccf
@@ -18,13 +18,13 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=DC,LJG
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=250000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=false
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLifeSeconds=700
 jcs.default.elementattributes.IdleTime=1800
@@ -37,14 +37,14 @@
 # ################# CACHE REGIONS AVAILABLE ###################
 # Regions preconfigured for caching
 jcs.region.testCache1=DC,LJG
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=250000
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache1.cacheattributes.UseMemoryShrinker=false
 jcs.region.testCache1.cacheattributes.ShrinkerIntervalSeconds=30
 jcs.region.testCache1.cacheattributes.MaxMemoryIdleTimeSeconds=300
 jcs.region.testCache1.cacheattributes.MaxSpoolPerRun=100
-jcs.region.testCache1.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache1.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache1.elementattributes.IsEternal=false
 jcs.region.testCache1.elementattributes.MaxLifeSeconds=60000
 jcs.region.testCache1.elementattributes.IsLateral=true
@@ -55,16 +55,16 @@
 # #############################################################
 # ################# AUXILIARY CACHES AVAILABLE ################
 # Primary Disk Cache-- faster than the rest because of memory key storage
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=logs/rafJG1
 jcs.auxiliary.DC.attributes.MaxPurgatorySize=100000
 jcs.auxiliary.DC.attributes.MaxKeySize=500000
 jcs.auxiliary.DC.attributes.OptimizeAtRemoveCount=-1
 
 # Lateral JavaGroups Distribution
-jcs.auxiliary.LJG=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LJG.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LJG=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LJG.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LJG.attributes.TransmissionTypeName=JAVAGROUPS
 jcs.auxiliary.LJG.attributes.PutOnlyMode=true
 jcs.auxiliary.LJG.attributes.JGChannelProperties=UDP(mcast_addr=224.10.10.10;mcast_port=5555;ip_ttl=32):PING(timeout=3000;num_initial_members=6):FD(timeout=3000):VERIFY_SUSPECT(timeout=1500):pbcast.NAKACK(gc_lag=10;retransmit_timeout=600,1200,2400,4800):UNICAST(timeout=600,1200,2400,4800):pbcast.STABLE(desired_avg_gossip=10000):FRAG:pbcast.GMS(join_timeout=5000;join_retry_timeout=2000;shun=false;print_local_addr=true)
diff --git a/commons-jcs-core/src/test/conf/cacheLMD1.ccf b/commons-jcs-core/src/test/conf/cacheLMD1.ccf
index 14d3128..3df79a6 100644
--- a/commons-jcs-core/src/test/conf/cacheLMD1.ccf
+++ b/commons-jcs-core/src/test/conf/cacheLMD1.ccf
@@ -18,13 +18,13 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=DC,LJG
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=1000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=true
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLifeSeconds=600
 jcs.default.elementattributes.IdleTime=1800
@@ -38,15 +38,15 @@
 # Regions preconfigured for caching
 # LEAD_PRICE_CACHE_NAME
 jcs.region.LeadPrice=DC,LJG
-jcs.region.LeadPrice.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.LeadPrice.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.LeadPrice.cacheattributes.MaxObjects=1000
 # set it to zero, was 1000
-jcs.region.LeadPrice.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.LeadPrice.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.LeadPrice.cacheattributes.UseMemoryShrinker=true
 jcs.region.LeadPrice.cacheattributes.ShrinkerIntervalSeconds=30
 jcs.region.LeadPrice.cacheattributes.MaxMemoryIdleTimeSeconds=300
 jcs.region.LeadPrice.cacheattributes.MaxSpoolPerRun=100
-jcs.region.LeadPrice.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.LeadPrice.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.LeadPrice.elementattributes.IsEternal=false
 jcs.region.LeadPrice.elementattributes.MaxLifeSeconds=600
 jcs.region.LeadPrice.elementattributes.IsSpool=true
@@ -55,15 +55,15 @@
 
 # HOTEL_OPTION_CACHE_NAME
 jcs.region.HotelOption=DC,LJG
-jcs.region.HotelOption.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.HotelOption.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.HotelOption.cacheattributes.MaxObjects=1000
 # set it to zero, was 1000
-jcs.region.HotelOption.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.HotelOption.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.HotelOption.cacheattributes.UseMemoryShrinker=true
 jcs.region.HotelOption.cacheattributes.ShrinkerIntervalSeconds=30
 jcs.region.HotelOption.cacheattributes.MaxMemoryIdleTimeSeconds=300
 jcs.region.HotelOption.cacheattributes.MaxSpoolPerRun=100
-jcs.region.HotelOption.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.HotelOption.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.HotelOption.elementattributes.IsEternal=false
 jcs.region.HotelOption.elementattributes.MaxLifeSeconds=600
 jcs.region.HotelOption.elementattributes.IsSpool=true
@@ -75,16 +75,16 @@
 # ################# AUXILIARY CACHES AVAILABLE ################
 
 # Primary Disk Cache-- faster than the rest because of memory key storage
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=log/raf
 jcs.auxiliary.DC.attributes.MaxPurgatorySize=10000
 jcs.auxiliary.DC.attributes.MaxKeySize=10000
 jcs.auxiliary.DC.attributes.OptimizeAtRemoveCount=300000
 
 # Lateral JavaGroups Distribution
-jcs.auxiliary.LJG=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LJG.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LJG=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LJG.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LJG.attributes.TransmissionTypeName=JAVAGROUPS
 jcs.auxiliary.LJG.attributes.PutOnlyMode=true
 jcs.auxiliary.LJG.attributes.JGChannelProperties=UDP(mcast_addr=224.0.0.100;mcast_port=751):PING(timeout=3000):FD:STABLE:NAKACK:UNICAST:FRAG:FLUSH:GMS:VIEW_ENFORCER:QUEUE
diff --git a/commons-jcs-core/src/test/conf/cacheNA.ccf b/commons-jcs-core/src/test/conf/cacheNA.ccf
index 8993040..26e69af 100644
--- a/commons-jcs-core/src/test/conf/cacheNA.ccf
+++ b/commons-jcs-core/src/test/conf/cacheNA.ccf
@@ -18,13 +18,13 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=DC,RC
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=250000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=false
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLifeSeconds=700
 jcs.default.elementattributes.IdleTime=1800
@@ -37,14 +37,14 @@
 # ################# CACHE REGIONS AVAILABLE ###################
 # Regions preconfigured for caching
 jcs.region.testCache1=DC,RC
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=250000
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache1.cacheattributes.UseMemoryShrinker=false
 jcs.region.testCache1.cacheattributes.ShrinkerIntervalSeconds=30
 jcs.region.testCache1.cacheattributes.MaxMemoryIdleTimeSeconds=300
 jcs.region.testCache1.cacheattributes.MaxSpoolPerRun=100
-jcs.region.testCache1.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache1.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache1.elementattributes.IsEternal=false
 jcs.region.testCache1.elementattributes.MaxLifeSeconds=60000
 jcs.region.testCache1.elementattributes.IsLateral=true
@@ -55,16 +55,16 @@
 # #############################################################
 # ################# AUXILIARY CACHES AVAILABLE ################
 # Primary Disk Cache-- faster than the rest because of memory key storage
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=logs/rafNA
 jcs.auxiliary.DC.attributes.MaxPurgatorySize=100000
 jcs.auxiliary.DC.attributes.MaxKeySize=500000
 jcs.auxiliary.DC.attributes.OptimizeAtRemoveCount=-1
 
 # REMOTE SERVER RS1
-jcs.auxiliary.RC=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RC.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RC=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RC.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 # First server is primary, the rest will be tried in order if the primary fails
 jcs.auxiliary.RC.attributes.FailoverServers=localhost:1101,localhost:1102
 jcs.auxiliary.RC.attributes.LocalPort=1201
diff --git a/commons-jcs-core/src/test/conf/cacheNA2.ccf b/commons-jcs-core/src/test/conf/cacheNA2.ccf
index 505ba1b..003d356 100644
--- a/commons-jcs-core/src/test/conf/cacheNA2.ccf
+++ b/commons-jcs-core/src/test/conf/cacheNA2.ccf
@@ -18,13 +18,13 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=DC,RC
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=250000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=false
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLifeSeconds=700
 jcs.default.elementattributes.IdleTime=1800
@@ -37,14 +37,14 @@
 # ################# CACHE REGIONS AVAILABLE ###################
 # Regions preconfigured for caching
 jcs.region.testCache1=DC,RC
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=250000
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache1.cacheattributes.UseMemoryShrinker=false
 jcs.region.testCache1.cacheattributes.ShrinkerIntervalSeconds=30
 jcs.region.testCache1.cacheattributes.MaxMemoryIdleTimeSeconds=300
 jcs.region.testCache1.cacheattributes.MaxSpoolPerRun=100
-jcs.region.testCache1.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache1.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache1.elementattributes.IsEternal=false
 jcs.region.testCache1.elementattributes.MaxLifeSeconds=60000
 jcs.region.testCache1.elementattributes.IsLateral=true
@@ -55,16 +55,16 @@
 # #############################################################
 # ################# AUXILIARY CACHES AVAILABLE ################
 # Primary Disk Cache-- faster than the rest because of memory key storage
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=logs/rafNA
 jcs.auxiliary.DC.attributes.MaxPurgatorySize=100000
 jcs.auxiliary.DC.attributes.MaxKeySize=500000
 jcs.auxiliary.DC.attributes.OptimizeAtRemoveCount=-1
 
 # REMOTE SERVER RS1
-jcs.auxiliary.RC=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RC.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RC=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RC.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 # First server is primary, the rest will be tried in order if the primary fails
 jcs.auxiliary.RC.attributes.FailoverServers=server:1101
 jcs.auxiliary.RC.attributes.LocalPort=1201
diff --git a/commons-jcs-core/src/test/conf/cacheNA3.ccf b/commons-jcs-core/src/test/conf/cacheNA3.ccf
index e4ed2ec..bace6b7 100644
--- a/commons-jcs-core/src/test/conf/cacheNA3.ccf
+++ b/commons-jcs-core/src/test/conf/cacheNA3.ccf
@@ -18,13 +18,13 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=DC,RC
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=250000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=false
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLifeSeconds=700
 jcs.default.elementattributes.IdleTime=1800
@@ -37,14 +37,14 @@
 # ################# CACHE REGIONS AVAILABLE ###################
 # Regions preconfigured for caching
 jcs.region.testCache1=DC,RC
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=250000
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache1.cacheattributes.UseMemoryShrinker=false
 jcs.region.testCache1.cacheattributes.ShrinkerIntervalSeconds=30
 jcs.region.testCache1.cacheattributes.MaxMemoryIdleTimeSeconds=300
 jcs.region.testCache1.cacheattributes.MaxSpoolPerRun=100
-jcs.region.testCache1.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache1.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache1.elementattributes.IsEternal=false
 jcs.region.testCache1.elementattributes.MaxLifeSeconds=60000
 jcs.region.testCache1.elementattributes.IsLateral=true
@@ -55,16 +55,16 @@
 # #############################################################
 # ################# AUXILIARY CACHES AVAILABLE ################
 # Primary Disk Cache-- faster than the rest because of memory key storage
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=logs/rafNA
 jcs.auxiliary.DC.attributes.MaxPurgatorySize=100000
 jcs.auxiliary.DC.attributes.MaxKeySize=500000
 jcs.auxiliary.DC.attributes.OptimizeAtRemoveCount=-1
 
 # REMOTE SERVER RS1
-jcs.auxiliary.RC=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RC.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RC=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RC.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 # First server is primary, the rest will be tried in order if the primary fails
 jcs.auxiliary.RC.attributes.FailoverServers=server:1101
 jcs.auxiliary.RC.attributes.LocalPort=1200
diff --git a/commons-jcs-core/src/test/conf/cacheNB.ccf b/commons-jcs-core/src/test/conf/cacheNB.ccf
index f428a2e..cb9016c 100644
--- a/commons-jcs-core/src/test/conf/cacheNB.ccf
+++ b/commons-jcs-core/src/test/conf/cacheNB.ccf
@@ -18,13 +18,13 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=DC,RC
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=250000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=false
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLifeSeconds=700
 jcs.default.elementattributes.IdleTime=1800
@@ -37,14 +37,14 @@
 # ################# CACHE REGIONS AVAILABLE ###################
 # Regions preconfigured for caching
 jcs.region.testCache1=DC,RC
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=250000
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache1.cacheattributes.UseMemoryShrinker=false
 jcs.region.testCache1.cacheattributes.ShrinkerIntervalSeconds=30
 jcs.region.testCache1.cacheattributes.MaxMemoryIdleTimeSeconds=300
 jcs.region.testCache1.cacheattributes.MaxSpoolPerRun=100
-jcs.region.testCache1.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache1.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache1.elementattributes.IsEternal=false
 jcs.region.testCache1.elementattributes.MaxLifeSeconds=60000
 jcs.region.testCache1.elementattributes.IsLateral=true
@@ -55,16 +55,16 @@
 # #############################################################
 # ################# AUXILIARY CACHES AVAILABLE ################
 # Primary Disk Cache-- faster than the rest because of memory key storage
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=logs/rafNB
 jcs.auxiliary.DC.attributes.MaxPurgatorySize=100000
 jcs.auxiliary.DC.attributes.MaxKeySize=500000
 jcs.auxiliary.DC.attributes.OptimizeAtRemoveCount=-1
 
 # REMOTE SERVER -- RS2
-jcs.auxiliary.RC=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RC.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RC=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RC.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 # First server is primary, the rest will be tried in order if the primary fails
 jcs.auxiliary.RC.attributes.FailoverServers=localhost:1102,localhost:1101
 jcs.auxiliary.RC.attributes.LocalPort=1202
diff --git a/commons-jcs-core/src/test/conf/cacheRC.ccf b/commons-jcs-core/src/test/conf/cacheRC.ccf
index a59c887..5b273f8 100644
--- a/commons-jcs-core/src/test/conf/cacheRC.ccf
+++ b/commons-jcs-core/src/test/conf/cacheRC.ccf
@@ -18,13 +18,13 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=DC,RC
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=200001
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=true
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLife=700
 jcs.default.elementattributes.IdleTime=1800
@@ -37,27 +37,27 @@
 # ################# CACHE REGIONS AVAILABLE ###################
 # Regions preconfigured for caching
 jcs.region.testCache1=DC,RC
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=1000000
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache1.cacheattributes.UseMemoryShrinker=true
 jcs.region.testCache1.cacheattributes.ShrinkerIntervalSeconds=30
 jcs.region.testCache1.cacheattributes.MaxMemoryIdleTimeSeconds=300
 jcs.region.testCache1.cacheattributes.MaxSpoolPerRun=100
-jcs.region.testCache1.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache1.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache1.elementattributes.IsEternal=false
 jcs.region.testCache1.elementattributes.MaxLifeSeconds=60000
 jcs.region.testCache1.elementattributes.IsLateral=true
 jcs.region.testCache1.elementattributes.IsRemote=true
 
 jcs.region.testCache2=DC,RC
-jcs.region.testCache2.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache2.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache2.cacheattributes.MaxObjects=100
-jcs.region.testCache2.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache2.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache2.cacheattributes.UseMemoryShrinker=true
 jcs.region.testCache2.cacheattributes.MaxMemoryIdleTimeSeconds=1000
 jcs.region.testCache2.cacheattributes.ShrinkerIntervalSeconds=40
-jcs.region.testCache2.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache2.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache2.elementattributes.IsEternal=false
 jcs.region.testCache2.elementattributes.MaxLifeSeconds=600
 jcs.region.testCache2.elementattributes.IsSpool=true
@@ -65,13 +65,13 @@
 jcs.region.testCache2.elementattributes.IsLateral=true
 
 jcs.region.testCache3=
-jcs.region.testCache3.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache3.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache3.cacheattributes.MaxObjects=100000
-jcs.region.testCache3.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache3.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache3.cacheattributes.UseMemoryShrinker=false
 jcs.region.testCache3.cacheattributes.MaxMemoryIdleTimeSeconds=10
 jcs.region.testCache3.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.region.testCache3.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache3.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache3.elementattributes.IsEternal=false
 jcs.region.testCache3.elementattributes.MaxLifeSeconds=3600
 jcs.region.testCache3.elementattributes.IsSpool=true
@@ -83,23 +83,23 @@
 # ################# AUXILIARY CACHES AVAILABLE ################
 
 # Remote RMI cache without failover
-jcs.auxiliary.RGroup=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RGroup.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RGroup=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RGroup.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RGroup.attributes.RemoteTypeName=LOCAL
 jcs.auxiliary.RGroup.attributes.RemoteHost=localhost
 jcs.auxiliary.RGroup.attributes.RemotePort=1102
 jcs.auxiliary.RGroup.attributes.GetOnly=true
 
 # Remote RMI Cache set up to failover
-jcs.auxiliary.RFailover=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RFailover.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RFailover=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RFailover.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RFailover.attributes.RemoteTypeName=LOCAL
 jcs.auxiliary.RFailover.attributes.FailoverServers=localhost:1102
 jcs.auxiliary.RFailover.attributes.GetOnly=false
 
 # Primary Disk Cache-- faster than the rest because of memory key storage
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=target/test-sandbox/raf
 jcs.auxiliary.DC.attributes.MaxPurgatorySize=10000000
 jcs.auxiliary.DC.attributes.MaxKeySize=1000000
@@ -113,8 +113,8 @@
 # If you want to use a separate pool for each disk cache, either use
 # the single model or define a different auxiliary for each region and use the Pooled.
 # SINGLE is best unless you have a huge # of regions.
-jcs.auxiliary.DC2=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC2.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC2=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC2.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC2.attributes.DiskPath=target/test-sandbox/raf
 jcs.auxiliary.DC2.attributes.MaxPurgatorySize=10000
 jcs.auxiliary.DC2.attributes.MaxKeySize=10000
@@ -123,28 +123,28 @@
 jcs.auxiliary.DC2.attributes.EventQueuePoolName=disk_cache_event_queue
 
 # Berkeley DB JE
-jcs.auxiliary.JE=org.apache.commons.jcs.auxiliary.disk.bdbje.BDBJECacheFactory
-jcs.auxiliary.JE.attributes=org.apache.commons.jcs.auxiliary.disk.bdbje.BDBJECacheAttributes
+jcs.auxiliary.JE=org.apache.commons.jcs3.auxiliary.disk.bdbje.BDBJECacheFactory
+jcs.auxiliary.JE.attributes=org.apache.commons.jcs3.auxiliary.disk.bdbje.BDBJECacheAttributes
 jcs.auxiliary.JE.attributes.DiskPath=target/test-sandbox/bdbje-disk-cache-conc
 # the minimum cache size is 1024
 jcs.auxiliary.indexedDiskCache.attributes.CacheSize=1024
 # jcs.auxiliary.indexedDiskCache.attributes.CachePercent=0
 
 # HSQL Disk Cache -- too slow as is
-jcs.auxiliary.HDC=org.apache.commons.jcs.auxiliary.disk.hsql.HSQLCacheFactory
-jcs.auxiliary.HDC.attributes=org.apache.commons.jcs.auxiliary.disk.hsql.HSQLCacheAttributes
+jcs.auxiliary.HDC=org.apache.commons.jcs3.auxiliary.disk.hsql.HSQLCacheFactory
+jcs.auxiliary.HDC.attributes=org.apache.commons.jcs3.auxiliary.disk.hsql.HSQLCacheAttributes
 jcs.auxiliary.HDC.attributes.DiskPath=@project_home_f@hsql
 
 # JISP Disk Cache -- save memory with disk key storage
-jcs.auxiliary.JDC=org.apache.commons.jcs.auxiliary.disk.jisp.JISPCacheFactory
-jcs.auxiliary.JDC.attributes=org.apache.commons.jcs.auxiliary.disk.jisp.JISPCacheAttributes
+jcs.auxiliary.JDC=org.apache.commons.jcs3.auxiliary.disk.jisp.JISPCacheFactory
+jcs.auxiliary.JDC.attributes=org.apache.commons.jcs3.auxiliary.disk.jisp.JISPCacheAttributes
 jcs.auxiliary.JDC.attributes.DiskPath=@project_home_f@raf
 jcs.auxiliary.JDC.attributes.ClearOnStart=false
 
 # need to make put or invalidate an option
 # just a remove lock to add
-jcs.auxiliary.RC=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RC.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RC=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RC.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RC.attributes.FailoverServers=localhost:1101,localhost:1102
 jcs.auxiliary.RC.attributes.LocalPort=1201
 jcs.auxiliary.RC.attributes.RemoveUponRemotePut=false
@@ -156,42 +156,42 @@
 jcs.auxiliary.RC.attributes.GetOnly=false
 
 # unreliable
-jcs.auxiliary.LUDP=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LUDP.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LUDP=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LUDP.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LUDP.attributes.TransmissionTypeName=UDP
 jcs.auxiliary.LUDP.attributes.UdpMulticastAddr=228.5.6.7
 jcs.auxiliary.LUDP.attributes.UdpMulticastPort=6789
 
-jcs.auxiliary.LJG=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LJG.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LJG=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LJG.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LJG.attributes.TransmissionTypeName=JAVAGROUPS
 jcs.auxiliary.LJG.attributes.PutOnlyMode=true
 jcs.auxiliary.LJG.attributes.JGChannelProperties = UDP(mcast_addr=224.0.0.100;mcast_port=751):PING(timeout=3000):FD:STABLE:NAKACK:UNICAST:FRAG:FLUSH:GMS:VIEW_ENFORCER:QUEUE
 
 
-jcs.auxiliary.JG = org.apache.commons.jcs.auxiliary.javagroups.JavaGroupsCacheFactory
-jcs.auxiliary.JG.attributes = org.apache.commons.jcs.auxiliary.javagroups.JavaGroupsCacheAttributes
+jcs.auxiliary.JG = org.apache.commons.jcs3.auxiliary.javagroups.JavaGroupsCacheFactory
+jcs.auxiliary.JG.attributes = org.apache.commons.jcs3.auxiliary.javagroups.JavaGroupsCacheAttributes
 jcs.auxiliary.JG.attributes.ChannelFactoryClassName = org.javagroups.JChannelFactory
 jcs.auxiliary.JG.attributes.ChannelProperties = UDP(mcast_addr=224.0.0.100;mcast_port=7501):PING:FD:STABLE:NAKACK:UNICAST:FRAG:FLUSH:GMS:VIEW_ENFORCER:QUEUE
 
 
 # almost complete
-jcs.auxiliary.LTCP=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LTCP.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LTCP=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LTCP.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LTCP.attributes.TransmissionTypeName=TCP
 jcs.auxiliary.LTCP.attributes.TcpServers=localhost:1112
 jcs.auxiliary.LTCP.attributes.TcpListenerPort=1111
 jcs.auxiliary.LTCP.attributes.PutOnlyMode=true
 
-jcs.auxiliary.LTCP2=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LTCP2.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LTCP2=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LTCP2.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LTCP2.attributes.TransmissionTypeName=TCP
 jcs.auxiliary.LTCP2.attributes.TcpServers=localhost:1112
 jcs.auxiliary.LTCP2.attributes.TcpListenerPort=1111
 jcs.auxiliary.LTCP2.attributes.PutOnlyMode=true
 
-jcs.auxiliary.XMLRPC=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.XMLRPC.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.XMLRPC=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.XMLRPC.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.XMLRPC.attributes.TransmissionTypeName=XMLRPC
 jcs.auxiliary.XMLRPC.attributes.HttpServers=localhost:8182
 jcs.auxiliary.XMLRPC.attributes.HttpListenerPort=8181
@@ -200,8 +200,8 @@
 
 # example of how to configure the http version of the lateral cache
 # not converteed to new cache
-jcs.auxiliary.LCHTTP=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LCHTTP.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LCHTTP=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LCHTTP.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LCHTTP.attributes.TransmissionType=HTTP
 jcs.auxiliary.LCHTTP.attributes.httpServers=localhost:8080,localhost:7001,localhost:80
 jcs.auxiliary.LCHTTP.attributes.httpReceiveServlet=/cache/LateralCacheReceiverServlet
diff --git a/commons-jcs-core/src/test/conf/cacheRC1.ccf b/commons-jcs-core/src/test/conf/cacheRC1.ccf
index 09890e3..71a2a4c 100644
--- a/commons-jcs-core/src/test/conf/cacheRC1.ccf
+++ b/commons-jcs-core/src/test/conf/cacheRC1.ccf
@@ -18,30 +18,30 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=DC
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=200000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 # #############################################################
 # ################# CACHE REGIONS AVAILABLE ###################
 jcs.region.testCache1=RC
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=200000
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache1.elementattributes.IsLateral=true
 jcs.region.testCache1.elementattributes.IsRemote=true
 
 # #############################################################
 # ################# AUXILIARY CACHES AVAILABLE ################
 # standard disk cache
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=${user.dir}/raf1
 
 # need to make put or invalidate an option
 # just a remove lock to add
-jcs.auxiliary.RC=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RC.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RC=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RC.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RC.attributes.FailoverServers=localhost:1101,localhost:1102
 jcs.auxiliary.RC.attributes.LocalPort=1201
 jcs.auxiliary.RC.attributes.RemoveUponRemotePut=false
diff --git a/commons-jcs-core/src/test/conf/cacheRC2.ccf b/commons-jcs-core/src/test/conf/cacheRC2.ccf
index 935236c..58e9750 100644
--- a/commons-jcs-core/src/test/conf/cacheRC2.ccf
+++ b/commons-jcs-core/src/test/conf/cacheRC2.ccf
@@ -18,30 +18,30 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=DC
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=200000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 # #############################################################
 # ################# CACHE REGIONS AVAILABLE ###################
 jcs.region.testCache1=RC
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=200000
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache1.elementattributes.IsLateral=true
 jcs.region.testCache1.elementattributes.IsRemote=true
 
 # #############################################################
 # ################# AUXILIARY CACHES AVAILABLE ################
 # standard disk cache
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=${user.dir}/raf2
 
 # need to make put or invalidate an option
 # just a remove lock to add
-jcs.auxiliary.RC=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RC.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RC=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RC.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RC.attributes.FailoverServers=localhost:1102,localhost:1101
 jcs.auxiliary.RC.attributes.LocalPort=1202
 jcs.auxiliary.RC.attributes.RemoveUponRemotePut=false
diff --git a/commons-jcs-core/src/test/conf/cacheRCN1.ccf b/commons-jcs-core/src/test/conf/cacheRCN1.ccf
index 7406467..e5cd48a 100644
--- a/commons-jcs-core/src/test/conf/cacheRCN1.ccf
+++ b/commons-jcs-core/src/test/conf/cacheRCN1.ccf
@@ -18,29 +18,29 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=DC
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=200000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 # #############################################################
 # ################# CACHE REGIONS AVAILABLE ###################
 jcs.region.testCache1=RC
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=200000
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache1.elementattributes.IsLateral=true
 jcs.region.testCache1.elementattributes.IsRemote=true
 
 # #############################################################
 # ################# AUXILIARY CACHES AVAILABLE ################
 # standard disk cache
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=${user.dir}/raf1
 
 # This remote client does not receive
-jcs.auxiliary.RC=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RC.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RC=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RC.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RC.attributes.FailoverServers=localhost:1101,localhost:1102
 jcs.auxiliary.RC.attributes.LocalPort=1201
 jcs.auxiliary.RC.attributes.RemoveUponRemotePut=false
diff --git a/commons-jcs-core/src/test/conf/cacheRCN2.ccf b/commons-jcs-core/src/test/conf/cacheRCN2.ccf
index 9ff900c..b52f532 100644
--- a/commons-jcs-core/src/test/conf/cacheRCN2.ccf
+++ b/commons-jcs-core/src/test/conf/cacheRCN2.ccf
@@ -18,29 +18,29 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=DC
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=200000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 # #############################################################
 # ################# CACHE REGIONS AVAILABLE ###################
 jcs.region.testCache1=RC
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=200000
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache1.elementattributes.IsLateral=true
 jcs.region.testCache1.elementattributes.IsRemote=true
 
 # #############################################################
 # ################# AUXILIARY CACHES AVAILABLE ################
 # standard disk cache
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=${user.dir}/raf2
 
 # This remote client does not receive
-jcs.auxiliary.RC=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RC.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RC=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RC.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RC.attributes.FailoverServers=localhost:1102,localhost:1101
 jcs.auxiliary.RC.attributes.LocalPort=1202
 jcs.auxiliary.RC.attributes.RemoveUponRemotePut=false
diff --git a/commons-jcs-core/src/test/conf/cacheRCSimple.ccf b/commons-jcs-core/src/test/conf/cacheRCSimple.ccf
index 14366f9..f3ac654 100644
--- a/commons-jcs-core/src/test/conf/cacheRCSimple.ccf
+++ b/commons-jcs-core/src/test/conf/cacheRCSimple.ccf
@@ -18,13 +18,13 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=RC
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=200001
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=true
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLife=700
 jcs.default.elementattributes.IdleTime=1800
@@ -37,14 +37,14 @@
 # ################# CACHE REGIONS AVAILABLE ###################
 # Regions preconfigured for caching
 jcs.region.testCache1=RC
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=1000000
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache1.cacheattributes.UseMemoryShrinker=true
 jcs.region.testCache1.cacheattributes.ShrinkerIntervalSeconds=30
 jcs.region.testCache1.cacheattributes.MaxMemoryIdleTimeSeconds=300
 jcs.region.testCache1.cacheattributes.MaxSpoolPerRun=100
-jcs.region.testCache1.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache1.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache1.elementattributes.IsEternal=false
 jcs.region.testCache1.elementattributes.MaxLifeSeconds=60000
 jcs.region.testCache1.elementattributes.IsLateral=true
@@ -54,23 +54,23 @@
 # ################# AUXILIARY CACHES AVAILABLE ################
 
 # Remote RMI cache without failover
-jcs.auxiliary.RGroup=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RGroup.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RGroup=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RGroup.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RGroup.attributes.RemoteTypeName=LOCAL
 jcs.auxiliary.RGroup.attributes.RemoteHost=localhost
 jcs.auxiliary.RGroup.attributes.RemotePort=1102
 jcs.auxiliary.RGroup.attributes.GetOnly=true
 
 # Remote RMI Cache set up to failover
-jcs.auxiliary.RFailover=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RFailover.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RFailover=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RFailover.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RFailover.attributes.RemoteTypeName=LOCAL
 jcs.auxiliary.RFailover.attributes.FailoverServers=localhost:1102
 jcs.auxiliary.RFailover.attributes.GetOnly=false
 
 # Primary Disk Cache-- faster than the rest because of memory key storage
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=target/test-sandbox/raf
 jcs.auxiliary.DC.attributes.MaxPurgatorySize=10000000
 jcs.auxiliary.DC.attributes.MaxKeySize=1000000
@@ -84,8 +84,8 @@
 # If you want to use a separate pool for each disk cache, either use
 # the single model or define a different auxiliary for each region and use the Pooled.
 # SINGLE is best unless you have a huge # of regions.
-jcs.auxiliary.DC2=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC2.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC2=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC2.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC2.attributes.DiskPath=target/test-sandbox/raf
 jcs.auxiliary.DC2.attributes.MaxPurgatorySize=10000
 jcs.auxiliary.DC2.attributes.MaxKeySize=10000
@@ -94,28 +94,28 @@
 jcs.auxiliary.DC2.attributes.EventQueuePoolName=disk_cache_event_queue
 
 # Berkeley DB JE
-jcs.auxiliary.JE=org.apache.commons.jcs.auxiliary.disk.bdbje.BDBJECacheFactory
-jcs.auxiliary.JE.attributes=org.apache.commons.jcs.auxiliary.disk.bdbje.BDBJECacheAttributes
+jcs.auxiliary.JE=org.apache.commons.jcs3.auxiliary.disk.bdbje.BDBJECacheFactory
+jcs.auxiliary.JE.attributes=org.apache.commons.jcs3.auxiliary.disk.bdbje.BDBJECacheAttributes
 jcs.auxiliary.JE.attributes.DiskPath=target/test-sandbox/bdbje-disk-cache-conc
 # the minimum cache size is 1024
 jcs.auxiliary.indexedDiskCache.attributes.CacheSize=1024
 # jcs.auxiliary.indexedDiskCache.attributes.CachePercent=0
 
 # HSQL Disk Cache -- too slow as is
-jcs.auxiliary.HDC=org.apache.commons.jcs.auxiliary.disk.hsql.HSQLCacheFactory
-jcs.auxiliary.HDC.attributes=org.apache.commons.jcs.auxiliary.disk.hsql.HSQLCacheAttributes
+jcs.auxiliary.HDC=org.apache.commons.jcs3.auxiliary.disk.hsql.HSQLCacheFactory
+jcs.auxiliary.HDC.attributes=org.apache.commons.jcs3.auxiliary.disk.hsql.HSQLCacheAttributes
 jcs.auxiliary.HDC.attributes.DiskPath=@project_home_f@hsql
 
 # JISP Disk Cache -- save memory with disk key storage
-jcs.auxiliary.JDC=org.apache.commons.jcs.auxiliary.disk.jisp.JISPCacheFactory
-jcs.auxiliary.JDC.attributes=org.apache.commons.jcs.auxiliary.disk.jisp.JISPCacheAttributes
+jcs.auxiliary.JDC=org.apache.commons.jcs3.auxiliary.disk.jisp.JISPCacheFactory
+jcs.auxiliary.JDC.attributes=org.apache.commons.jcs3.auxiliary.disk.jisp.JISPCacheAttributes
 jcs.auxiliary.JDC.attributes.DiskPath=@project_home_f@raf
 jcs.auxiliary.JDC.attributes.ClearOnStart=false
 
 # need to make put or invalidate an option
 # just a remove lock to add
-jcs.auxiliary.RC=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RC.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RC=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RC.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RC.attributes.FailoverServers=localhost:1101
 # ,localhost:1102
 jcs.auxiliary.RC.attributes.LocalPort=1201
@@ -128,42 +128,42 @@
 jcs.auxiliary.RC.attributes.GetOnly=false
 
 # unreliable
-jcs.auxiliary.LUDP=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LUDP.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LUDP=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LUDP.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LUDP.attributes.TransmissionTypeName=UDP
 jcs.auxiliary.LUDP.attributes.UdpMulticastAddr=228.5.6.7
 jcs.auxiliary.LUDP.attributes.UdpMulticastPort=6789
 
-jcs.auxiliary.LJG=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LJG.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LJG=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LJG.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LJG.attributes.TransmissionTypeName=JAVAGROUPS
 jcs.auxiliary.LJG.attributes.PutOnlyMode=true
 jcs.auxiliary.LJG.attributes.JGChannelProperties = UDP(mcast_addr=224.0.0.100;mcast_port=751):PING(timeout=3000):FD:STABLE:NAKACK:UNICAST:FRAG:FLUSH:GMS:VIEW_ENFORCER:QUEUE
 
 
-jcs.auxiliary.JG = org.apache.commons.jcs.auxiliary.javagroups.JavaGroupsCacheFactory
-jcs.auxiliary.JG.attributes = org.apache.commons.jcs.auxiliary.javagroups.JavaGroupsCacheAttributes
+jcs.auxiliary.JG = org.apache.commons.jcs3.auxiliary.javagroups.JavaGroupsCacheFactory
+jcs.auxiliary.JG.attributes = org.apache.commons.jcs3.auxiliary.javagroups.JavaGroupsCacheAttributes
 jcs.auxiliary.JG.attributes.ChannelFactoryClassName = org.javagroups.JChannelFactory
 jcs.auxiliary.JG.attributes.ChannelProperties = UDP(mcast_addr=224.0.0.100;mcast_port=7501):PING:FD:STABLE:NAKACK:UNICAST:FRAG:FLUSH:GMS:VIEW_ENFORCER:QUEUE
 
 
 # almost complete
-jcs.auxiliary.LTCP=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LTCP.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LTCP=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LTCP.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LTCP.attributes.TransmissionTypeName=TCP
 jcs.auxiliary.LTCP.attributes.TcpServers=localhost:1112
 jcs.auxiliary.LTCP.attributes.TcpListenerPort=1111
 jcs.auxiliary.LTCP.attributes.PutOnlyMode=true
 
-jcs.auxiliary.LTCP2=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LTCP2.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LTCP2=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LTCP2.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LTCP2.attributes.TransmissionTypeName=TCP
 jcs.auxiliary.LTCP2.attributes.TcpServers=localhost:1112
 jcs.auxiliary.LTCP2.attributes.TcpListenerPort=1111
 jcs.auxiliary.LTCP2.attributes.PutOnlyMode=true
 
-jcs.auxiliary.XMLRPC=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.XMLRPC.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.XMLRPC=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.XMLRPC.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.XMLRPC.attributes.TransmissionTypeName=XMLRPC
 jcs.auxiliary.XMLRPC.attributes.HttpServers=localhost:8182
 jcs.auxiliary.XMLRPC.attributes.HttpListenerPort=8181
@@ -172,8 +172,8 @@
 
 # example of how to configure the http version of the lateral cache
 # not converteed to new cache
-jcs.auxiliary.LCHTTP=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LCHTTP.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LCHTTP=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LCHTTP.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LCHTTP.attributes.TransmissionType=HTTP
 jcs.auxiliary.LCHTTP.attributes.httpServers=localhost:8080,localhost:7001,localhost:80
 jcs.auxiliary.LCHTTP.attributes.httpReceiveServlet=/cache/LateralCacheReceiverServlet
diff --git a/commons-jcs-core/src/test/conf/cacheRC_CEL.ccf b/commons-jcs-core/src/test/conf/cacheRC_CEL.ccf
index 59b7913..6e76421 100644
--- a/commons-jcs-core/src/test/conf/cacheRC_CEL.ccf
+++ b/commons-jcs-core/src/test/conf/cacheRC_CEL.ccf
@@ -18,13 +18,13 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=DC,RC
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=200001
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=true
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLife=700
 jcs.default.elementattributes.IdleTime=1800
@@ -37,27 +37,27 @@
 # ################# CACHE REGIONS AVAILABLE ###################
 # Regions preconfigured for caching
 jcs.region.testCache1=DC,RC
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=1000000
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache1.cacheattributes.UseMemoryShrinker=true
 jcs.region.testCache1.cacheattributes.ShrinkerIntervalSeconds=30
 jcs.region.testCache1.cacheattributes.MaxMemoryIdleTimeSeconds=300
 jcs.region.testCache1.cacheattributes.MaxSpoolPerRun=100
-jcs.region.testCache1.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache1.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache1.elementattributes.IsEternal=false
 jcs.region.testCache1.elementattributes.MaxLifeSeconds=60000
 jcs.region.testCache1.elementattributes.IsLateral=true
 jcs.region.testCache1.elementattributes.IsRemote=true
 
 jcs.region.testCache2=DC,RC
-jcs.region.testCache2.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache2.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache2.cacheattributes.MaxObjects=100
-jcs.region.testCache2.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache2.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache2.cacheattributes.UseMemoryShrinker=true
 jcs.region.testCache2.cacheattributes.MaxMemoryIdleTimeSeconds=1000
 jcs.region.testCache2.cacheattributes.ShrinkerIntervalSeconds=40
-jcs.region.testCache2.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache2.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache2.elementattributes.IsEternal=false
 jcs.region.testCache2.elementattributes.MaxLifeSeconds=600
 jcs.region.testCache2.elementattributes.IsSpool=true
@@ -65,13 +65,13 @@
 jcs.region.testCache2.elementattributes.IsLateral=true
 
 jcs.region.testCache3=
-jcs.region.testCache3.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache3.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache3.cacheattributes.MaxObjects=100000
-jcs.region.testCache3.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache3.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache3.cacheattributes.UseMemoryShrinker=false
 jcs.region.testCache3.cacheattributes.MaxMemoryIdleTimeSeconds=10
 jcs.region.testCache3.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.region.testCache3.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache3.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache3.elementattributes.IsEternal=false
 jcs.region.testCache3.elementattributes.MaxLifeSeconds=3600
 jcs.region.testCache3.elementattributes.IsSpool=true
@@ -83,8 +83,8 @@
 # ################# AUXILIARY CACHES AVAILABLE ################
 
 # Primary Disk Cache-- faster than the rest because of memory key storage
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=target/test-sandbox/raf
 jcs.auxiliary.DC.attributes.MaxPurgatorySize=10000000
 jcs.auxiliary.DC.attributes.MaxKeySize=1000000
@@ -93,8 +93,8 @@
 
 # need to make put or invalidate an option
 # just a remove lock to add
-jcs.auxiliary.RC=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RC.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RC=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RC.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RC.attributes.FailoverServers=localhost:1101,localhost:1102
 jcs.auxiliary.RC.attributes.LocalPort=1201
 jcs.auxiliary.RC.attributes.RemoveUponRemotePut=false
@@ -104,7 +104,7 @@
 jcs.auxiliary.RC.attributes.GetTimeoutMillis=500
 jcs.auxiliary.RC.attributes.ThreadPoolName=remote_cache_client
 jcs.auxiliary.RC.attributes.GetOnly=false
-jcs.auxiliary.RC.cacheeventlogger=org.apache.commons.jcs.engine.logging.CacheEventLoggerDebugLogger
+jcs.auxiliary.RC.cacheeventlogger=org.apache.commons.jcs3.engine.logging.CacheEventLoggerDebugLogger
 jcs.auxiliary.RC.cacheeventlogger.attributes.logCategoryName=test.RCCEventLogCategory
 
 
diff --git a/commons-jcs-core/src/test/conf/cacheRHTTP.ccf b/commons-jcs-core/src/test/conf/cacheRHTTP.ccf
index 9c2cff4..88bdd8a 100644
--- a/commons-jcs-core/src/test/conf/cacheRHTTP.ccf
+++ b/commons-jcs-core/src/test/conf/cacheRHTTP.ccf
@@ -18,13 +18,13 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=RC
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=0
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=true
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLife=700
 jcs.default.elementattributes.IdleTime=1800
@@ -34,7 +34,7 @@
 
 
 ## The Http Remote Cache Client
-jcs.auxiliary.RC=org.apache.commons.jcs.auxiliary.remote.http.client.RemoteHttpCacheFactory
-jcs.auxiliary.RC.attributes=org.apache.commons.jcs.auxiliary.remote.http.client.RemoteHttpCacheAttributes
+jcs.auxiliary.RC=org.apache.commons.jcs3.auxiliary.remote.http.client.RemoteHttpCacheFactory
+jcs.auxiliary.RC.attributes=org.apache.commons.jcs3.auxiliary.remote.http.client.RemoteHttpCacheAttributes
 jcs.auxiliary.RC.attributes.url=http://localhost:8000/jcs-app/RemoteCache
 jcs.auxiliary.RC.attributes.maxConnectionsPerHost=500
diff --git a/commons-jcs-core/src/test/conf/cacheTCP1.ccf b/commons-jcs-core/src/test/conf/cacheTCP1.ccf
index 5fbfc03..11760d3 100644
--- a/commons-jcs-core/src/test/conf/cacheTCP1.ccf
+++ b/commons-jcs-core/src/test/conf/cacheTCP1.ccf
@@ -18,13 +18,13 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=LTCP
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=200001
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=true
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLifeSeconds=700
 jcs.default.elementattributes.IdleTime=1800
@@ -37,14 +37,14 @@
 # ################# CACHE REGIONS AVAILABLE ###################
 # Regions preconfigured for caching
 jcs.region.testCache1=LTCP
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=1000
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache1.cacheattributes.UseMemoryShrinker=true
 jcs.region.testCache1.cacheattributes.ShrinkerIntervalSeconds=30
 jcs.region.testCache1.cacheattributes.MaxMemoryIdleTimeSeconds=300
 jcs.region.testCache1.cacheattributes.MaxSpoolPerRun=100
-jcs.region.testCache1.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache1.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache1.elementattributes.IsEternal=false
 jcs.region.testCache1.elementattributes.MaxLifeSeconds=60000
 jcs.region.testCache1.elementattributes.IsLateral=true
@@ -55,8 +55,8 @@
 # ################# AUXILIARY CACHES AVAILABLE ################
 
 # TCP
-jcs.auxiliary.LTCP=org.apache.commons.jcs.auxiliary.lateral.socket.tcp.LateralTCPCacheFactory
-jcs.auxiliary.LTCP.attributes=org.apache.commons.jcs.auxiliary.lateral.socket.tcp.TCPLateralCacheAttributes
+jcs.auxiliary.LTCP=org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.LateralTCPCacheFactory
+jcs.auxiliary.LTCP.attributes=org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.TCPLateralCacheAttributes
 jcs.auxiliary.LTCP.attributes.TcpListenerPort=1118
 jcs.auxiliary.LTCP.attributes.UdpDiscoveryAddr=228.5.6.8
 jcs.auxiliary.LTCP.attributes.UdpDiscoveryPort=9780
diff --git a/commons-jcs-core/src/test/conf/cacheTCP2.ccf b/commons-jcs-core/src/test/conf/cacheTCP2.ccf
index 4f03854..c281feb 100644
--- a/commons-jcs-core/src/test/conf/cacheTCP2.ccf
+++ b/commons-jcs-core/src/test/conf/cacheTCP2.ccf
@@ -18,13 +18,13 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=200001
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=true
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLifeSeconds=700
 jcs.default.elementattributes.IdleTime=1800
@@ -37,28 +37,28 @@
 # ################# CACHE REGIONS AVAILABLE ###################
 # Regions preconfigured for caching
 jcs.region.testCache1=LTCP
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=1000
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache1.cacheattributes.UseMemoryShrinker=true
 jcs.region.testCache1.cacheattributes.ShrinkerIntervalSeconds=30
 jcs.region.testCache1.cacheattributes.MaxMemoryIdleTimeSeconds=300
 jcs.region.testCache1.cacheattributes.MaxSpoolPerRun=100
-jcs.region.testCache1.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache1.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache1.elementattributes.IsEternal=false
 jcs.region.testCache1.elementattributes.MaxLifeSeconds=60000
 jcs.region.testCache1.elementattributes.IsLateral=true
 jcs.region.testCache1.elementattributes.IsRemote=true
 
 jcs.region.testCache2=LTCP
-jcs.region.testCache2.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache2.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache2.cacheattributes.MaxObjects=1000
-jcs.region.testCache2.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache2.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache2.cacheattributes.UseMemoryShrinker=true
 jcs.region.testCache2.cacheattributes.ShrinkerIntervalSeconds=30
 jcs.region.testCache2.cacheattributes.MaxMemoryIdleTimeSeconds=300
 jcs.region.testCache2.cacheattributes.MaxSpoolPerRun=100
-jcs.region.testCache2.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache2.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache2.elementattributes.IsEternal=false
 jcs.region.testCache2.elementattributes.MaxLifeSeconds=60000
 jcs.region.testCache2.elementattributes.IsLateral=true
@@ -68,8 +68,8 @@
 # ################# AUXILIARY CACHES AVAILABLE ################
 
 # TCP
-jcs.auxiliary.LTCP=org.apache.commons.jcs.auxiliary.lateral.socket.tcp.LateralTCPCacheFactory
-jcs.auxiliary.LTCP.attributes=org.apache.commons.jcs.auxiliary.lateral.socket.tcp.TCPLateralCacheAttributes
+jcs.auxiliary.LTCP=org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.LateralTCPCacheFactory
+jcs.auxiliary.LTCP.attributes=org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.TCPLateralCacheAttributes
 jcs.auxiliary.LTCP.attributes.TcpListenerPort=1119
 jcs.auxiliary.LTCP.attributes.UdpDiscoveryAddr=228.5.6.8
 jcs.auxiliary.LTCP.attributes.UdpDiscoveryPort=9780
diff --git a/commons-jcs-core/src/test/conf/cacheTCP3.ccf b/commons-jcs-core/src/test/conf/cacheTCP3.ccf
index 15a119d..f9c67b3 100644
--- a/commons-jcs-core/src/test/conf/cacheTCP3.ccf
+++ b/commons-jcs-core/src/test/conf/cacheTCP3.ccf
@@ -18,13 +18,13 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=200001
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=true
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLifeSeconds=700
 jcs.default.elementattributes.IdleTime=1800
@@ -37,28 +37,28 @@
 # ################# CACHE REGIONS AVAILABLE ###################
 # Regions preconfigured for caching
 jcs.region.testCache1=LTCP
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=1000
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache1.cacheattributes.UseMemoryShrinker=true
 jcs.region.testCache1.cacheattributes.ShrinkerIntervalSeconds=30
 jcs.region.testCache1.cacheattributes.MaxMemoryIdleTimeSeconds=300
 jcs.region.testCache1.cacheattributes.MaxSpoolPerRun=100
-jcs.region.testCache1.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache1.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache1.elementattributes.IsEternal=false
 jcs.region.testCache1.elementattributes.MaxLifeSeconds=60000
 jcs.region.testCache1.elementattributes.IsLateral=true
 jcs.region.testCache1.elementattributes.IsRemote=true
 
 jcs.region.testCache2=LTCP
-jcs.region.testCache2.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache2.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache2.cacheattributes.MaxObjects=1000
-jcs.region.testCache2.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache2.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache2.cacheattributes.UseMemoryShrinker=true
 jcs.region.testCache2.cacheattributes.ShrinkerIntervalSeconds=30
 jcs.region.testCache2.cacheattributes.MaxMemoryIdleTimeSeconds=300
 jcs.region.testCache2.cacheattributes.MaxSpoolPerRun=100
-jcs.region.testCache2.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache2.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache2.elementattributes.IsEternal=false
 jcs.region.testCache2.elementattributes.MaxLifeSeconds=60000
 jcs.region.testCache2.elementattributes.IsLateral=true
@@ -68,8 +68,8 @@
 # ################# AUXILIARY CACHES AVAILABLE ################
 
 # almost complete
-jcs.auxiliary.LTCP=org.apache.commons.jcs.auxiliary.lateral.socket.tcp.LateralTCPCacheFactory
-jcs.auxiliary.LTCP.attributes=org.apache.commons.jcs.auxiliary.lateral.socket.tcp.TCPLateralCacheAttributes
+jcs.auxiliary.LTCP=org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.LateralTCPCacheFactory
+jcs.auxiliary.LTCP.attributes=org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.TCPLateralCacheAttributes
 jcs.auxiliary.LTCP.attributes.TransmissionTypeName=TCP
 # jcs.auxiliary.LTCP.attributes.TcpServers=
 jcs.auxiliary.LTCP.attributes.TcpListenerPort=1120
diff --git a/commons-jcs-core/src/test/conf/cacheTCP4.ccf b/commons-jcs-core/src/test/conf/cacheTCP4.ccf
index 69c6c1c..de3210c 100644
--- a/commons-jcs-core/src/test/conf/cacheTCP4.ccf
+++ b/commons-jcs-core/src/test/conf/cacheTCP4.ccf
@@ -18,13 +18,13 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=200001
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=true
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLifeSeconds=700
 jcs.default.elementattributes.IdleTime=1800
@@ -37,14 +37,14 @@
 # ################# CACHE REGIONS AVAILABLE ###################
 # Regions preconfigured for caching
 jcs.region.testCache1=LTCP
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=1000
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache1.cacheattributes.UseMemoryShrinker=true
 jcs.region.testCache1.cacheattributes.ShrinkerIntervalSeconds=30
 jcs.region.testCache1.cacheattributes.MaxMemoryIdleTimeSeconds=300
 jcs.region.testCache1.cacheattributes.MaxSpoolPerRun=100
-jcs.region.testCache1.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache1.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache1.elementattributes.IsEternal=false
 jcs.region.testCache1.elementattributes.MaxLifeSeconds=60000
 jcs.region.testCache1.elementattributes.IsLateral=true
@@ -55,8 +55,8 @@
 # ################# AUXILIARY CACHES AVAILABLE ################
 
 # almost complete
-jcs.auxiliary.LTCP=org.apache.commons.jcs.auxiliary.lateral.socket.tcp.LateralTCPCacheFactory
-jcs.auxiliary.LTCP.attributes=org.apache.commons.jcs.auxiliary.lateral.socket.tcp.TCPLateralCacheAttributes
+jcs.auxiliary.LTCP=org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.LateralTCPCacheFactory
+jcs.auxiliary.LTCP.attributes=org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.TCPLateralCacheAttributes
 jcs.auxiliary.LTCP.attributes.TransmissionTypeName=TCP
 # jcs.auxiliary.LTCP.attributes.TcpServers=
 jcs.auxiliary.LTCP.attributes.TcpListenerPort=1121
diff --git a/commons-jcs-core/src/test/conf/log4j.properties b/commons-jcs-core/src/test/conf/log4j.properties
index 90ea9c8..87047c6 100644
--- a/commons-jcs-core/src/test/conf/log4j.properties
+++ b/commons-jcs-core/src/test/conf/log4j.properties
@@ -42,28 +42,28 @@
 log4j.category.org.jgroups=WARN
 
 log4j.category.org.apache.commons.jcs=INFO
-log4j.category.org.apache.commons.jcs.engine=INFO
-# log4j.category.org.apache.commons.jcs.acess=WARN,WF
-# log4j.category.org.apache.commons.jcs.engine.control=WARN,WF
+log4j.category.org.apache.commons.jcs3.engine=INFO
+# log4j.category.org.apache.commons.jcs3.acess=WARN,WF
+# log4j.category.org.apache.commons.jcs3.engine.control=WARN,WF
 
-# log4j.logger.org.apache.commons.jcs.engine.memory.shrinking=INFO
-# log4j.logger.org.apache.commons.jcs.auxiliary.disk=INFO
-log4j.logger.org.apache.commons.jcs.auxiliary.disk.indexed=INFO
+# log4j.logger.org.apache.commons.jcs3.engine.memory.shrinking=INFO
+# log4j.logger.org.apache.commons.jcs3.auxiliary.disk=INFO
+log4j.logger.org.apache.commons.jcs3.auxiliary.disk.indexed=INFO
 
-# log4j.category.org.apache.commons.jcs.config=WARN,A1
+# log4j.category.org.apache.commons.jcs3.config=WARN,A1
 
-# log4j.category.org.apache.commons.jcs.auxiliary=INFO
-# log4j.category.org.apache.commons.jcs.auxiliary.disk=WARN,WF
-# log4j.category.org.apache.commons.jcs.auxiliary.lateral=INFO
-# log4j.category.org.apache.commons.jcs.auxiliary.lateral.javagroups=INFO
-# log4j.category.org.apache.commons.jcs.auxiliary.lateral.xmlrpc=INFO
-# log4j.category.org.apache.commons.jcs.auxiliary.remote=INFO
-# log4j.category.org.apache.commons.jcs.auxiliary.remote.RemoteCacheFailoverRunner=INFO
-# log4j.category.org.apache.commons.jcs.auxiliary.remote.RemoteCacheListener=DEBUG
-# log4j.category.org.apache.commons.jcs.auxiliary.remote.RemoteCacheManager=INFO
-# log4j.category.org.apache.commons.jcs.auxiliary.remote.server.RemoteCacheServer=DEBUG
-# log4j.category.org.apache.commons.jcs.auxiliary.remote.server=INFO
-# log4j.category.org.apache.commons.jcs.utils=WARN,WF
-# log4j.category.org.apache.commons.jcs.utils.discovery=DEBUG
+# log4j.category.org.apache.commons.jcs3.auxiliary=INFO
+# log4j.category.org.apache.commons.jcs3.auxiliary.disk=WARN,WF
+# log4j.category.org.apache.commons.jcs3.auxiliary.lateral=INFO
+# log4j.category.org.apache.commons.jcs3.auxiliary.lateral.javagroups=INFO
+# log4j.category.org.apache.commons.jcs3.auxiliary.lateral.xmlrpc=INFO
+# log4j.category.org.apache.commons.jcs3.auxiliary.remote=INFO
+# log4j.category.org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFailoverRunner=INFO
+# log4j.category.org.apache.commons.jcs3.auxiliary.remote.RemoteCacheListener=DEBUG
+# log4j.category.org.apache.commons.jcs3.auxiliary.remote.RemoteCacheManager=INFO
+# log4j.category.org.apache.commons.jcs3.auxiliary.remote.server.RemoteCacheServer=DEBUG
+# log4j.category.org.apache.commons.jcs3.auxiliary.remote.server=INFO
+# log4j.category.org.apache.commons.jcs3.utils=WARN,WF
+# log4j.category.org.apache.commons.jcs3.utils.discovery=DEBUG
 
-log4j.category.org.apache.commons.jcs.utils=INFO
+log4j.category.org.apache.commons.jcs3.utils=INFO
diff --git a/commons-jcs-core/src/test/conf/remote.cache.ccf b/commons-jcs-core/src/test/conf/remote.cache.ccf
index 40e6261..c069d15 100644
--- a/commons-jcs-core/src/test/conf/remote.cache.ccf
+++ b/commons-jcs-core/src/test/conf/remote.cache.ccf
@@ -21,20 +21,20 @@
 registry.port=1101
 remote.cache.service.port=1101
 remote.cluster.LocalClusterConsistency=true
-#remote.cacheeventlogger=org.apache.commons.jcs.engine.logging.CacheEventLoggerDebugLogger
+#remote.cacheeventlogger=org.apache.commons.jcs3.engine.logging.CacheEventLoggerDebugLogger
 #remote.cacheeventlogger.attributes.logCategoryName=TestEventLogCategory
 
 # #############################################################
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=DC,RCluster
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=200000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=true
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLife=7000
 jcs.default.elementattributes.IdleTime=1800
@@ -46,15 +46,15 @@
 # #############################################################
 # ################# CACHE REGIONS AVAILABLE ###################
 jcs.region.testCache1=DC,RCluster
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=200002
 
 
 # #############################################################
 # ################# AUXILIARY CACHES AVAILABLE ################
 # server to update for clustering -- remote.cache2.ccf(1102)  and remote.cache3.ccf(1103)
-jcs.auxiliary.RCluster=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RCluster.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RCluster=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RCluster.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RCluster.attributes.RemoteTypeName=CLUSTER
 jcs.auxiliary.RCluster.attributes.RemoveUponRemotePut=false
 jcs.auxiliary.RCluster.attributes.ClusterServers=localhost:1102
@@ -62,7 +62,7 @@
 jcs.auxiliary.RCluster.attributes.LocalClusterConsistency=true
 
 
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=@project_home@/raf/remote
 
diff --git a/commons-jcs-core/src/test/conf/remote.cache2.ccf b/commons-jcs-core/src/test/conf/remote.cache2.ccf
index 803cdd6..352347c 100644
--- a/commons-jcs-core/src/test/conf/remote.cache2.ccf
+++ b/commons-jcs-core/src/test/conf/remote.cache2.ccf
@@ -29,13 +29,13 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=DC,RCluster
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=200000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=true
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLife=7000
 jcs.default.elementattributes.IdleTime=1800
@@ -47,15 +47,15 @@
 # #############################################################
 # ################# CACHE REGIONS AVAILABLE ###################
 jcs.region.testCache1=DC,RCluster
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=200000
 
 
 # #############################################################
 # ################# AUXILIARY CACHES AVAILABLE ################
 # server to update for clustering -- remote.cache1.ccf(1101)  and remote.cache3.ccf(1103)
-jcs.auxiliary.RCluster=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RCluster.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RCluster=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RCluster.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RCluster.attributes.RemoteTypeName=CLUSTER
 jcs.auxiliary.RCluster.attributes.RemoveUponRemotePut=false
 jcs.auxiliary.RCluster.attributes.ClusterServers=localhost:1101
@@ -63,8 +63,8 @@
 jcs.auxiliary.RCluster.attributes.LocalClusterConsistency=true
 
 # disk cache
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=@project_home@/raf/remote-rc2
 
 
diff --git a/commons-jcs-core/src/test/conf/remote.cache3.ccf b/commons-jcs-core/src/test/conf/remote.cache3.ccf
index 116a208..13108b8 100644
--- a/commons-jcs-core/src/test/conf/remote.cache3.ccf
+++ b/commons-jcs-core/src/test/conf/remote.cache3.ccf
@@ -29,14 +29,14 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=DC,RCluster
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=1000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=true
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLife=7000
 jcs.default.elementattributes.IdleTime=1800
@@ -48,15 +48,15 @@
 # #############################################################
 # ################# CACHE REGIONS AVAILABLE ###################
 jcs.region.testCache1=DC,RCluster
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=1000
 
 
 # #############################################################
 # ################# AUXILIARY CACHES AVAILABLE ################
 # server to update for clustering -- remote.cache1.ccf(1101) and remote.cache2.ccf(1102)
-jcs.auxiliary.RCluster=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RCluster.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RCluster=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RCluster.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RCluster.attributes.RemoteTypeName=CLUSTER
 jcs.auxiliary.RCluster.attributes.RemoveUponRemotePut=false
 jcs.auxiliary.RCluster.attributes.ClusterServers=localhost:1101,localhost:1102
@@ -65,7 +65,7 @@
 
 
 # disk cache
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=@project_home@/raf/remote
 
diff --git a/commons-jcs-core/src/test/conf/remote.cacheCEL.ccf b/commons-jcs-core/src/test/conf/remote.cacheCEL.ccf
index a2bf52a..3b24f0f 100644
--- a/commons-jcs-core/src/test/conf/remote.cacheCEL.ccf
+++ b/commons-jcs-core/src/test/conf/remote.cacheCEL.ccf
@@ -21,20 +21,20 @@
 registry.port=1101
 remote.cache.service.port=1101
 remote.cluster.LocalClusterConsistency=true
-remote.cacheeventlogger=org.apache.commons.jcs.engine.logging.CacheEventLoggerDebugLogger
+remote.cacheeventlogger=org.apache.commons.jcs3.engine.logging.CacheEventLoggerDebugLogger
 remote.cacheeventlogger.attributes.logCategoryName=test.RCSEventLogCategory
 
 # #############################################################
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=DC,RCluster
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=200000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=true
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLife=7000
 jcs.default.elementattributes.IdleTime=1800
@@ -46,15 +46,15 @@
 # #############################################################
 # ################# CACHE REGIONS AVAILABLE ###################
 jcs.region.testCache1=DC,RCluster
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=200002
 
 
 # #############################################################
 # ################# AUXILIARY CACHES AVAILABLE ################
 # server to update for clustering -- remote.cache2.ccf(1102)  and remote.cache3.ccf(1103)
-jcs.auxiliary.RCluster=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RCluster.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RCluster=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RCluster.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RCluster.attributes.RemoteTypeName=CLUSTER
 jcs.auxiliary.RCluster.attributes.RemoveUponRemotePut=false
 jcs.auxiliary.RCluster.attributes.ClusterServers=localhost:1102
@@ -62,7 +62,7 @@
 jcs.auxiliary.RCluster.attributes.LocalClusterConsistency=true
 
 
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=@project_home@/raf/remote
 
diff --git a/commons-jcs-core/src/test/conf/remote.cacheCEL_CSF.ccf b/commons-jcs-core/src/test/conf/remote.cacheCEL_CSF.ccf
index 892a435..3876eb1 100644
--- a/commons-jcs-core/src/test/conf/remote.cacheCEL_CSF.ccf
+++ b/commons-jcs-core/src/test/conf/remote.cacheCEL_CSF.ccf
@@ -21,21 +21,21 @@
 registry.port=1101
 jcs.remotecache.serverattributes.servicePort=1301
 jcs.remotecache.serverattributes.localClusterConsistency=true
-jcs.remotecache.customrmisocketfactory=org.apache.commons.jcs.auxiliary.remote.server.MockRMISocketFactory
-jcs.remotecache.cacheeventlogger=org.apache.commons.jcs.engine.logging.CacheEventLoggerDebugLogger
+jcs.remotecache.customrmisocketfactory=org.apache.commons.jcs3.auxiliary.remote.server.MockRMISocketFactory
+jcs.remotecache.cacheeventlogger=org.apache.commons.jcs3.engine.logging.CacheEventLoggerDebugLogger
 jcs.remotecache.cacheeventlogger.attributes.logCategoryName=test.RCSEventLogCategory
 
 # #############################################################
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=DC,RCluster
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=200000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=true
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLife=7000
 jcs.default.elementattributes.IdleTime=1800
@@ -47,15 +47,15 @@
 # #############################################################
 # ################# CACHE REGIONS AVAILABLE ###################
 jcs.region.testCache1=DC,RCluster
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=200002
 
 
 # #############################################################
 # ################# AUXILIARY CACHES AVAILABLE ################
 # server to update for clustering -- remote.cache2.ccf(1102)  and remote.cache3.ccf(1103)
-jcs.auxiliary.RCluster=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RCluster.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RCluster=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RCluster.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RCluster.attributes.RemoteTypeName=CLUSTER
 jcs.auxiliary.RCluster.attributes.RemoveUponRemotePut=false
 jcs.auxiliary.RCluster.attributes.ClusterServers=localhost:1102
@@ -63,7 +63,7 @@
 jcs.auxiliary.RCluster.attributes.LocalClusterConsistency=true
 
 
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=@project_home@/raf/remote
 
diff --git a/commons-jcs-core/src/test/conf/remote.cacheRS1.ccf b/commons-jcs-core/src/test/conf/remote.cacheRS1.ccf
index 6c525d6..5e9c2e1 100644
--- a/commons-jcs-core/src/test/conf/remote.cacheRS1.ccf
+++ b/commons-jcs-core/src/test/conf/remote.cacheRS1.ccf
@@ -29,13 +29,13 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=DC,RCluster
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=250000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=false
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLifeSeconds=700
 jcs.default.elementattributes.IdleTime=1800
@@ -48,14 +48,14 @@
 # ################# CACHE REGIONS AVAILABLE ###################
 # Regions preconfigured for caching
 jcs.region.testCache1=DC,RCluster
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=250000
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache1.cacheattributes.UseMemoryShrinker=false
 jcs.region.testCache1.cacheattributes.ShrinkerIntervalSeconds=30
 jcs.region.testCache1.cacheattributes.MaxMemoryIdleTimeSeconds=300
 jcs.region.testCache1.cacheattributes.MaxSpoolPerRun=100
-jcs.region.testCache1.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache1.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache1.elementattributes.IsEternal=false
 jcs.region.testCache1.elementattributes.MaxLifeSeconds=60000
 jcs.region.testCache1.elementattributes.IsLateral=true
@@ -66,16 +66,16 @@
 # #############################################################
 # ################# AUXILIARY CACHES AVAILABLE ################
 # Primary Disk Cache-- faster than the rest because of memory key storage
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=logs/rafRS1
 jcs.auxiliary.DC.attributes.MaxPurgatorySize=100000
 jcs.auxiliary.DC.attributes.MaxKeySize=500000
 jcs.auxiliary.DC.attributes.OptimizeAtRemoveCount=-1
 
 # RS2 SERVER to update for clustering
-jcs.auxiliary.RCluster=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RCluster.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RCluster=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RCluster.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RCluster.attributes.RemoteTypeName=CLUSTER
 jcs.auxiliary.RCluster.attributes.RemoveUponRemotePut=false
 jcs.auxiliary.RCluster.attributes.ClusterServers=localhost:1102
diff --git a/commons-jcs-core/src/test/conf/remote.cacheRS2.ccf b/commons-jcs-core/src/test/conf/remote.cacheRS2.ccf
index dd561c9..ac10baf 100644
--- a/commons-jcs-core/src/test/conf/remote.cacheRS2.ccf
+++ b/commons-jcs-core/src/test/conf/remote.cacheRS2.ccf
@@ -29,13 +29,13 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=DC,RCluster
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=250000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=false
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLifeSeconds=700
 jcs.default.elementattributes.IdleTime=1800
@@ -48,14 +48,14 @@
 # ################# CACHE REGIONS AVAILABLE ###################
 # Regions preconfigured for caching
 jcs.region.testCache1=DC,RCluster
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=250000
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache1.cacheattributes.UseMemoryShrinker=false
 jcs.region.testCache1.cacheattributes.ShrinkerIntervalSeconds=30
 jcs.region.testCache1.cacheattributes.MaxMemoryIdleTimeSeconds=300
 jcs.region.testCache1.cacheattributes.MaxSpoolPerRun=100
-jcs.region.testCache1.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache1.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache1.elementattributes.IsEternal=false
 jcs.region.testCache1.elementattributes.MaxLifeSeconds=60000
 jcs.region.testCache1.elementattributes.IsLateral=true
@@ -66,16 +66,16 @@
 # #############################################################
 # ################# AUXILIARY CACHES AVAILABLE ################
 # Primary Disk Cache-- faster than the rest because of memory key storage
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=logs/rafRS2
 jcs.auxiliary.DC.attributes.MaxPurgatorySize=100000
 jcs.auxiliary.DC.attributes.MaxKeySize=500000
 jcs.auxiliary.DC.attributes.OptimizeAtRemoveCount=-1
 
 # RS1 SERVER to update for clustering
-jcs.auxiliary.RCluster=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RCluster.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RCluster=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RCluster.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RCluster.attributes.RemoteTypeName=CLUSTER
 jcs.auxiliary.RCluster.attributes.RemoveUponRemotePut=false
 jcs.auxiliary.RCluster.attributes.ClusterServers=localhost:1101
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/ConcurrentRemovalLoadTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/ConcurrentRemovalLoadTest.java
deleted file mode 100644
index 7cfcc44..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/ConcurrentRemovalLoadTest.java
+++ /dev/null
@@ -1,137 +0,0 @@
-package org.apache.commons.jcs;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.extensions.ActiveTestSuite;
-import junit.framework.Test;
-import junit.framework.TestCase;
-
-/**
- * Test which exercises the hierarchical removal when the cache is active.
- */
-public class ConcurrentRemovalLoadTest
-    extends TestCase
-{
-    /**
-     * A unit test suite for JUnit. This verifies that we can remove hierarchically while the region
-     * is active.
-     * @return The test suite
-     */
-    public static Test suite()
-    {
-        ActiveTestSuite suite = new ActiveTestSuite();
-
-        suite.addTest( new RemovalTestUtil( "testRemoveCache1" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                runTestPutThenRemoveCategorical( 0, 200 );
-            }
-        } );
-
-        suite.addTest( new RemovalTestUtil( "testPutCache1" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                runPutInRange( 300, 400 );
-            }
-        } );
-
-        suite.addTest( new RemovalTestUtil( "testPutCache2" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                runPutInRange( 401, 600 );
-            }
-        } );
-
-        // stomp on previous put
-        suite.addTest( new RemovalTestUtil( "testPutCache3" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                runPutInRange( 401, 600 );
-            }
-        } );
-
-        suite.addTest( new RemovalTestUtil( "testRemoveCache1" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                runTestPutThenRemoveCategorical( 601, 700 );
-            }
-        } );
-
-        suite.addTest( new RemovalTestUtil( "testRemoveCache1" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                runTestPutThenRemoveCategorical( 701, 800 );
-            }
-        } );
-
-        suite.addTest( new RemovalTestUtil( "testRemoveCache1" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                runTestPutThenRemoveCategorical( 901, 1000 );
-            }
-        } );
-
-        suite.addTest( new RemovalTestUtil( "testPutCache2" )
-        {
-            // verify that there are no errors with concurrent gets.
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                runGetInRange( 0, 1000, false );
-            }
-        } );
-        return suite;
-    }
-
-    /**
-     * Test setup
-     * <p>
-     * @throws Exception
-     */
-    @Override
-    public void setUp()
-        throws Exception
-    {
-        JCS.setConfigFilename( "/TestRemoval.ccf" );
-        JCS.getInstance( "testCache1" );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/JCSCacheElementRetrievalUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/JCSCacheElementRetrievalUnitTest.java
deleted file mode 100644
index 8b90b3d..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/JCSCacheElementRetrievalUnitTest.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package org.apache.commons.jcs;
-
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-
-/**
- * @author Aaron Smuts
- *
- */
-public class JCSCacheElementRetrievalUnitTest
-    extends TestCase
-{
-    /**
-     *
-     * @throws Exception
-     */
-    public void testSimpleElementRetrieval()
-        throws Exception
-    {
-        CacheAccess<String, String> jcs = JCS.getInstance( "testCache1" );
-
-        jcs.put( "test_key", "test_data" );
-
-        long now = System.currentTimeMillis();
-        ICacheElement<String, String> elem = jcs.getCacheElement( "test_key" );
-        assertEquals( "Name wasn't right", "testCache1", elem.getCacheName() );
-
-        long diff = now - elem.getElementAttributes().getCreateTime();
-        assertTrue( "Create time should have been at or after the call", diff >= 0 );
-
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/JCSConcurrentCacheAccessUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/JCSConcurrentCacheAccessUnitTest.java
deleted file mode 100644
index 48932cb..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/JCSConcurrentCacheAccessUnitTest.java
+++ /dev/null
@@ -1,182 +0,0 @@
-package org.apache.commons.jcs;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.apache.commons.jcs.access.GroupCacheAccess;
-import org.apache.commons.jcs.access.exception.CacheException;
-
-import junit.framework.TestCase;
-
-/**
- * Test Case for JCS-73, modeled after the Groovy code by Alexander Kleymenov
- *
- * @author Thomas Vandahl
- *
- */
-public class JCSConcurrentCacheAccessUnitTest extends TestCase
-{
-    private final static int THREADS = 20;
-    private final static int LOOPS = 10000;
-
-    /**
-     * the cache instance
-     */
-    protected GroupCacheAccess<Integer, String> cache;
-
-    /**
-     * the group name
-     */
-    protected String group = "group";
-
-    /**
-     * the error count
-     */
-    protected AtomicInteger errcount;
-
-    /**
-     * Collect all value mismatches
-     */
-    protected List<String> valueMismatchList;
-
-    @Override
-	protected void setUp() throws Exception
-	{
-        super.setUp();
-        JCS.setConfigFilename( "/TestJCS-73.ccf" );
-        cache = JCS.getGroupCacheInstance( "cache" );
-        errcount = new AtomicInteger(0);
-        valueMismatchList = new CopyOnWriteArrayList<>();
-	}
-
-    @Override
-    protected void tearDown()
-        throws Exception
-    {
-        super.tearDown();
-        cache.clear();
-        cache.dispose();
-    }
-
-    /**
-     * Worker thread
-     */
-    protected class Worker extends Thread
-    {
-    	@Override
-		public void run()
-		{
-			String name = getName();
-
-			for (int idx = 0; idx < LOOPS; idx++)
-			{
-				if (idx > 0)
-				{
-					// get previously stored value
-		            String res = cache.getFromGroup(Integer.valueOf(idx-1), group);
-
-		            if (res == null)
-		            {
-		                // null value got inspite of the fact it was placed in cache!
-		                System.out.println("ERROR: for " + idx + " in " + name);
-		                errcount.incrementAndGet();
-
-		                // try to get the value again:
-		                int n = 5;
-		                while (n-- > 0)
-		                {
-		                    res = cache.getFromGroup(Integer.valueOf(idx-1), group);
-		                    if (res != null)
-		                    {
-		                        // the value finally appeared in cache
-		                    	System.out.println("ERROR FIXED for " + idx + ": " + res + " " + name);
-		                    	errcount.decrementAndGet();
-		                        break;
-		                    }
-
-		                    System.out.println("ERROR STILL PERSISTS for " + idx + " in " + name);
-		                    try
-		                    {
-								Thread.sleep(1000);
-							}
-		                    catch (InterruptedException e)
-							{
-								// continue
-							}
-		                }
-		            }
-
-		            if (!String.valueOf(idx-1).equals(res))
-		            {
-		                valueMismatchList.add(String.format("Values do not match: %s - %s", String.valueOf(idx-1), res));
-		            }
-				}
-
-				 // put value in the cache
-		        try
-		        {
-					cache.putInGroup(Integer.valueOf(idx), group, String.valueOf(idx));
-				}
-		        catch (CacheException e)
-		        {
-		        	// continue
-				}
-
-//		        if ((idx % 1000) == 0)
-//		        {
-//		        	System.out.println(name + " " + idx);
-//		        }
-			}
-
-		}
-    }
-
-	/**
-     *
-     * @throws Exception
-     */
-    public void testConcurrentAccess()
-        throws Exception
-    {
-    	Worker[] worker = new Worker[THREADS];
-
-        for (int i = 0; i < THREADS; i++)
-        {
-        	worker[i] = new Worker();
-        	worker[i].start();
-        }
-
-        for (int i = 0; i < THREADS; i++)
-        {
-        	worker[i].join();
-        }
-
-        assertEquals("Error count should be 0",  0, errcount.intValue());
-        for (String msg : valueMismatchList)
-        {
-            System.out.println(msg);
-        }
-        assertEquals("Value mismatch count should be 0",  0, valueMismatchList.size());
-    }
-
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/JCSLightLoadUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/JCSLightLoadUnitTest.java
deleted file mode 100644
index 92e1011..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/JCSLightLoadUnitTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package org.apache.commons.jcs;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.access.CacheAccess;
-
-import junit.framework.TestCase;
-
-/**
- * Runs a few thousand queries.
- */
-public class JCSLightLoadUnitTest
-    extends TestCase
-{
-    /** number to use for the test */
-    private static int items = 20000;
-
-    /**
-     * Test setup
-     * @throws Exception
-     */
-    @Override
-    public void setUp()
-        throws Exception
-    {
-        JCS.setConfigFilename( "/TestSimpleLoad.ccf" );
-        JCS.getInstance( "testCache1" );
-    }
-
-    /**
-     * A unit test for JUnit
-     * @throws Exception Description of the Exception
-     */
-    public void testSimpleLoad()
-        throws Exception
-    {
-        CacheAccess<String, String> jcs = JCS.getInstance( "testCache1" );
-        //        ICompositeCacheAttributes cattr = jcs.getCacheAttributes();
-        //        cattr.setMaxObjects( 20002 );
-        //        jcs.setCacheAttributes( cattr );
-
-        for ( int i = 1; i <= items; i++ )
-        {
-            jcs.put( i + ":key", "data" + i );
-        }
-
-        for ( int i = items; i > 0; i-- )
-        {
-            String res = jcs.get( i + ":key" );
-            assertNotNull( "[" + i + ":key] should not be null", res );
-        }
-
-        // test removal
-        jcs.remove( "300:key" );
-        assertNull( jcs.get( "300:key" ) );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/JCSRemovalSimpleConcurrentTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/JCSRemovalSimpleConcurrentTest.java
deleted file mode 100644
index 92a3a56..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/JCSRemovalSimpleConcurrentTest.java
+++ /dev/null
@@ -1,192 +0,0 @@
-package org.apache.commons.jcs;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.access.CacheAccess;
-
-/**
- * Verify that basic removal functionality works.
- */
-public class JCSRemovalSimpleConcurrentTest
-    extends TestCase
-{
-    /**
-     * @param testName
-     */
-    public JCSRemovalSimpleConcurrentTest( String testName )
-    {
-        super( testName );
-    }
-
-    /**
-     * Test setup
-     * <p>
-     * @throws Exception
-     */
-    @Override
-    public void setUp()
-        throws Exception
-    {
-        JCS.setConfigFilename( "/TestRemoval.ccf" );
-        JCS.getInstance( "testCache1" );
-    }
-
-    /**
-     * Main method passes this test to the text test runner.
-     * <p>
-     * @param args
-     */
-    public static void main( String args[] )
-    {
-        String[] testCaseName = { JCSRemovalSimpleConcurrentTest.class.getName() };
-        junit.textui.TestRunner.main( testCaseName );
-    }
-
-    /**
-     * Verify that 2 level deep hierchical removal works.
-     * <p>
-     * @throws Exception
-     */
-    public void testTwoDeepRemoval()
-        throws Exception
-    {
-        int count = 500;
-        CacheAccess<String, String> jcs = JCS.getInstance( "testCache1" );
-
-        for ( int i = 0; i <= count; i++ )
-        {
-            jcs.put( "key:" + i + ":anotherpart", "data" + i );
-        }
-
-        for ( int i = count; i >= 0; i-- )
-        {
-            String res = jcs.get( "key:" + i + ":anotherpart" );
-            assertNotNull( "[key:" + i + ":anotherpart] should not be null, " + jcs.getStats(), res );
-        }
-
-        for ( int i = 0; i <= count; i++ )
-        {
-            jcs.remove( "key:" + i + ":" );
-            assertNull( jcs.getStats(), jcs.get( "key:" + i + ":anotherpart" ) );
-        }
-
-    }
-
-    /**
-     * Verify that 1 level deep hierchical removal works.
-     *
-     * @throws Exception
-     */
-    public void testSingleDepthRemoval()
-        throws Exception
-    {
-
-        int count = 500;
-        CacheAccess<String, String> jcs = JCS.getInstance( "testCache1" );
-
-        for ( int i = 0; i <= count; i++ )
-        {
-            jcs.put( i + ":key", "data" + i );
-        }
-
-        for ( int i = count; i >= 0; i-- )
-        {
-            String res = jcs.get( i + ":key" );
-            assertNotNull( "[" + i + ":key] should not be null", res );
-        }
-
-        for ( int i = 0; i <= count; i++ )
-        {
-            jcs.remove( i + ":" );
-            assertNull( jcs.get( i + ":key" ) );
-        }
-    }
-
-    /**
-     * Verify that clear removes everyting as it should.
-     * <p>
-     * @throws Exception
-     */
-    public void testClear()
-        throws Exception
-    {
-
-        int count = 500;
-        CacheAccess<String, String> jcs = JCS.getInstance( "testCache1" );
-
-        for ( int i = 0; i <= count; i++ )
-        {
-            jcs.put( i + ":key", "data" + i );
-        }
-
-        for ( int i = count; i >= 0; i-- )
-        {
-            String res = jcs.get( i + ":key" );
-            assertNotNull( "[" + i + ":key] should not be null", res );
-        }
-        jcs.clear();
-
-        for ( int i = count; i >= 0; i-- )
-        {
-            String res = jcs.get( i + ":key" );
-            if ( res != null )
-            {
-                assertNull( "[" + i + ":key] should be null after remvoeall" + jcs.getStats(), res );
-            }
-        }
-    }
-
-    /**
-     * Verify that we can clear repeatedly without error.
-     *
-     * @throws Exception
-     */
-    public void testClearRepeatedlyWithoutError()
-        throws Exception
-    {
-        int count = 500;
-        CacheAccess<String, String> jcs = JCS.getInstance( "testCache1" );
-
-        jcs.clear();
-
-        for ( int i = 0; i <= count; i++ )
-        {
-            jcs.put( i + ":key", "data" + i );
-        }
-
-        for ( int i = count; i >= 0; i-- )
-        {
-            String res = jcs.get( i + ":key" );
-            assertNotNull( "[" + i + ":key] should not be null", res );
-        }
-
-        for ( int i = count; i >= 0; i-- )
-        {
-            jcs.put( i + ":key", "data" + i );
-            jcs.clear();
-            String res = jcs.get( i + ":key" );
-            if ( res != null )
-            {
-                assertNull( "[" + i + ":key] should be null after remvoeall" + jcs.getStats(), res );
-            }
-        }
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/JCSThrashTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/JCSThrashTest.java
deleted file mode 100644
index 42869ae..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/JCSThrashTest.java
+++ /dev/null
@@ -1,316 +0,0 @@
-package org.apache.commons.jcs;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.engine.stats.behavior.IStatElement;
-import org.apache.commons.jcs.engine.stats.behavior.IStats;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-import junit.framework.TestCase;
-
-/**
- * This is based on a test that was posted to the user's list:
- * <p>
- * http://www.opensubscriber.com/message/jcs-users@jakarta.apache.org/2435965.html
- */
-public class JCSThrashTest
-    extends TestCase
-{
-    /** The logger. */
-    private static final Log LOG = LogManager.getLog( JCSThrashTest.class.getName() );
-
-    /**
-     * the cache instance
-     */
-    protected CacheAccess<String, Serializable> jcs;
-
-    /**
-     * Sets up the test
-     * @throws Exception
-     */
-    @Override
-    protected void setUp()
-        throws Exception
-    {
-        super.setUp();
-        JCS.setConfigFilename( "/TestThrash.ccf" );
-        jcs = JCS.getInstance( "testcache" );
-    }
-
-    /**
-     * @throws Exception
-     */
-    @Override
-    protected void tearDown()
-        throws Exception
-    {
-        super.tearDown();
-        jcs.clear();
-        jcs.dispose();
-    }
-
-    /**
-     * Tests adding an entry.
-     * @throws Exception
-     */
-    public void testPut()
-        throws Exception
-    {
-        final String value = "value";
-        final String key = "key";
-
-        // Make sure the element is not found
-        assertEquals( 0, getListSize() );
-
-        assertNull( jcs.get( key ) );
-
-        jcs.put( key, value );
-
-        // Get the element
-        LOG.info( "jcs.getStats(): " + jcs.getStatistics() );
-        assertEquals( 1, getListSize() );
-        assertNotNull( jcs.get( key ) );
-        assertEquals( value, jcs.get( key ) );
-    }
-
-    /**
-     * Test elements can be removed from the store
-     * @throws Exception
-     */
-    public void testRemove()
-        throws Exception
-    {
-        jcs.put( "key1", "value1" );
-        assertEquals( 1, getListSize() );
-
-        jcs.remove( "key1" );
-        assertEquals( 0, getListSize() );
-
-        jcs.put( "key2", "value2" );
-        jcs.put( "key3", "value3" );
-        assertEquals( 2, getListSize() );
-
-        jcs.remove( "key2" );
-        assertEquals( 1, getListSize() );
-
-        // Try to remove an object that is not there in the store
-        jcs.remove( "key4" );
-        assertEquals( 1, getListSize() );
-    }
-
-    /**
-     * This does a bunch of work and then verifies that the memory has not grown by much. Most of
-     * the time the amount of memory used after the test is less.
-     * @throws Exception
-     */
-    public void testForMemoryLeaks()
-        throws Exception
-    {
-        long differenceMemoryCache = thrashCache();
-        LOG.info( "Memory Difference is: " + differenceMemoryCache );
-        assertTrue( differenceMemoryCache < 500000 );
-
-        //LOG.info( "Memory Used is: " + measureMemoryUse() );
-    }
-
-    /**
-     * @return time
-     * @throws Exception
-     */
-    protected long thrashCache()
-        throws Exception
-    {
-        long startingSize = measureMemoryUse();
-        LOG.info( "Memory Used is: " + startingSize );
-
-        final String value = "value";
-        final String key = "key";
-
-        // Add the entry
-        jcs.put( key, value );
-
-        // Create 15 threads that read the keys;
-        final List<Executable> executables = new ArrayList<>();
-        for ( int i = 0; i < 15; i++ )
-        {
-            final JCSThrashTest.Executable executable = new JCSThrashTest.Executable()
-            {
-                @Override
-                public void execute()
-                    throws Exception
-                {
-                    for ( int j = 0; j < 500; j++ )
-                    {
-                        final String keyj = "key" + j;
-                        jcs.get( keyj );
-                    }
-                    jcs.get( "key" );
-                }
-            };
-            executables.add( executable );
-        }
-
-        // Create 15 threads that are insert 500 keys with large byte[] as
-        // values
-        for ( int i = 0; i < 15; i++ )
-        {
-            final JCSThrashTest.Executable executable = new JCSThrashTest.Executable()
-            {
-                @Override
-                public void execute()
-                    throws Exception
-                {
-
-                    // Add a bunch of entries
-                    for ( int j = 0; j < 500; j++ )
-                    {
-                        // Use a random length value
-                        final String keyj = "key" + j;
-                        byte[] valuej = new byte[10000];
-                        jcs.put( keyj, valuej );
-                    }
-                }
-            };
-            executables.add( executable );
-        }
-
-        runThreads( executables );
-        jcs.clear();
-
-        long finishingSize = measureMemoryUse();
-        LOG.info( "Memory Used is: " + finishingSize );
-        return finishingSize - startingSize;
-    }
-
-    /**
-     * Runs a set of threads, for a fixed amount of time.
-     * <p>
-     * @param executables
-     * @throws Exception
-     */
-    protected void runThreads( final List<Executable> executables )
-        throws Exception
-    {
-
-        final long endTime = System.currentTimeMillis() + 10000;
-        final Throwable[] errors = new Throwable[1];
-
-        // Spin up the threads
-        final Thread[] threads = new Thread[executables.size()];
-        for ( int i = 0; i < threads.length; i++ )
-        {
-            final JCSThrashTest.Executable executable = executables.get( i );
-            threads[i] = new Thread()
-            {
-                @Override
-                public void run()
-                {
-                    try
-                    {
-                        // Run the thread until the given end time
-                        while ( System.currentTimeMillis() < endTime )
-                        {
-                            executable.execute();
-                        }
-                    }
-                    catch ( Throwable t )
-                    {
-                        // Hang on to any errors
-                        errors[0] = t;
-                    }
-                }
-            };
-            threads[i].start();
-        }
-
-        // Wait for the threads to finish
-        for ( int i = 0; i < threads.length; i++ )
-        {
-            threads[i].join();
-        }
-
-        // Throw any error that happened
-        if ( errors[0] != null )
-        {
-            throw new Exception( "Test thread failed.", errors[0] );
-        }
-    }
-
-    /**
-     * Measure memory used by the VM.
-     * <p>
-     * @return bytes
-     * @throws InterruptedException
-     */
-    protected long measureMemoryUse()
-        throws InterruptedException
-    {
-        System.gc();
-        Thread.sleep( 3000 );
-        System.gc();
-        return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
-    }
-
-    /**
-     * A runnable, that can throw an exception.
-     */
-    protected interface Executable
-    {
-        /**
-         * Executes this object.
-         * @throws Exception
-         */
-        void execute()
-            throws Exception;
-    }
-
-    /**
-     * @return size
-     */
-    private int getListSize()
-    {
-        final String listSize = "List Size";
-        final String lruMemoryCache = "LRU Memory Cache";
-        String result = "0";
-        List<IStats> istats = jcs.getStatistics().getAuxiliaryCacheStats();
-        for ( IStats istat : istats )
-        {
-            List<IStatElement<?>> statElements = istat.getStatElements();
-            if ( lruMemoryCache.equals( istat.getTypeName() ) )
-            {
-                for ( IStatElement<?> statElement : statElements )
-                {
-                    if ( listSize.equals( statElement.getName() ) )
-                    {
-                        result = statElement.getData().toString();
-                        break;
-                    }
-                }
-            }
-        }
-        return Integer.parseInt( result );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/JCSUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/JCSUnitTest.java
deleted file mode 100644
index 4d09330..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/JCSUnitTest.java
+++ /dev/null
@@ -1,89 +0,0 @@
-package org.apache.commons.jcs;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.Random;
-
-import org.apache.commons.jcs.access.CacheAccess;
-
-import junit.framework.TestCase;
-
-/**
- * Simple test for the JCS class.
- */
-public class JCSUnitTest
-    extends TestCase
-{
-    /** A random for key generation. */
-    Random random = new Random();
-
-    /**
-     * @throws Exception
-     */
-    public void testJCS()
-        throws Exception
-    {
-        CacheAccess<String, LinkedList<HashMap<String, String>>> jcs = JCS.getInstance( "testCache1" );
-
-        LinkedList<HashMap<String, String>> list = buildList();
-
-        jcs.put( "some:key", list );
-
-        assertEquals( list, jcs.get( "some:key" ) );
-    }
-
-    /**
-     * @return builds a list
-     */
-    private LinkedList<HashMap<String, String>> buildList()
-    {
-        LinkedList<HashMap<String, String>> list = new LinkedList<>();
-
-        for ( int i = 0; i < 100; i++ )
-        {
-            list.add( buildMap() );
-        }
-
-        return list;
-    }
-
-    /**
-     * @return a map
-     */
-    private HashMap<String, String> buildMap()
-    {
-        HashMap<String, String> map = new HashMap<>();
-
-        byte[] keyBytes = new byte[32];
-        byte[] valBytes = new byte[128];
-
-        for ( int i = 0; i < 10; i++ )
-        {
-            random.nextBytes( keyBytes );
-            random.nextBytes( valBytes );
-
-            map.put( new String( keyBytes ), new String( valBytes ) );
-        }
-
-        return map;
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/JCSvsHashtablePerformanceTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/JCSvsHashtablePerformanceTest.java
deleted file mode 100644
index cb0d4e4..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/JCSvsHashtablePerformanceTest.java
+++ /dev/null
@@ -1,180 +0,0 @@
-package org.apache.commons.jcs;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.Hashtable;
-
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-import junit.framework.TestCase;
-
-/**
- * This test ensures that basic memory operations are with a specified order of magnitude of the
- * java.util.Hashtable.
- * <p>
- * Currently JCS is under 2x a hashtable for gets, and under 1.2x for puts.
- */
-public class JCSvsHashtablePerformanceTest
-    extends TestCase
-{
-    /** jcs / hashtable */
-    float ratioPut = 0;
-
-    /** jcs / hashtable */
-    float ratioGet = 0;
-
-    /** ration goal */
-    float target = 3.50f;
-
-    /** Times to run the test */
-    int loops = 20;
-
-    /** how many puts and gets to run */
-    int tries = 50000;
-
-    /**
-     * A unit test for JUnit
-     * @throws Exception Description of the Exception
-     */
-    public void testSimpleLoad()
-        throws Exception
-    {
-        Log log1 = LogManager.getLog( LRUMemoryCache.class );
-        if ( log1.isDebugEnabled() )
-        {
-            System.out.println( "The log level must be at info or above for the a performance test." );
-            return;
-        }
-        Log log2 = LogManager.getLog( JCS.class );
-        if ( log2.isDebugEnabled() )
-        {
-            System.out.println( "The log level must be at info or above for the a performance test." );
-            return;
-        }
-        doWork();
-        assertTrue( this.ratioPut < target );
-        assertTrue( this.ratioGet < target );
-    }
-
-    /**
-     *
-     */
-    public void doWork()
-    {
-        long start = 0;
-        long end = 0;
-        long time = 0;
-        float tPer = 0;
-
-        long putTotalJCS = 0;
-        long getTotalJCS = 0;
-        long putTotalHashtable = 0;
-        long getTotalHashtable = 0;
-
-        try
-        {
-
-            JCS.setConfigFilename( "/TestJCSvHashtablePerf.ccf" );
-            CacheAccess<String, String> cache = JCS.getInstance( "testCache1" );
-
-            for ( int j = 0; j < loops; j++ )
-            {
-
-                String name = "JCS      ";
-                start = System.currentTimeMillis();
-                for ( int i = 0; i < tries; i++ )
-                {
-                    cache.put( "key:" + i, "data" + i );
-                }
-                end = System.currentTimeMillis();
-                time = end - start;
-                putTotalJCS += time;
-                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
-                System.out.println( name + " put time for " + tries + " = " + time + "; millis per = " + tPer );
-
-                start = System.currentTimeMillis();
-                for ( int i = 0; i < tries; i++ )
-                {
-                    cache.get( "key:" + i );
-                }
-                end = System.currentTimeMillis();
-                time = end - start;
-                getTotalJCS += time;
-                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
-                System.out.println( name + " get time for " + tries + " = " + time + "; millis per = " + tPer );
-
-                // /////////////////////////////////////////////////////////////
-                name = "Hashtable";
-                Hashtable<String, String> cache2 = new Hashtable<>();
-                start = System.currentTimeMillis();
-                for ( int i = 0; i < tries; i++ )
-                {
-                    cache2.put( "key:" + i, "data" + i );
-                }
-                end = System.currentTimeMillis();
-                time = end - start;
-                putTotalHashtable += time;
-                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
-                System.out.println( name + " put time for " + tries + " = " + time + "; millis per = " + tPer );
-
-                start = System.currentTimeMillis();
-                for ( int i = 0; i < tries; i++ )
-                {
-                    cache2.get( "key:" + i );
-                }
-                end = System.currentTimeMillis();
-                time = end - start;
-                getTotalHashtable += time;
-                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
-                System.out.println( name + " get time for " + tries + " = " + time + "; millis per = " + tPer );
-
-                System.out.println( "\n" );
-            }
-
-        }
-        catch ( Exception e )
-        {
-            e.printStackTrace( System.out );
-            System.out.println( e );
-        }
-
-        long putAvJCS = putTotalJCS / loops;
-        long getAvJCS = getTotalJCS / loops;
-        long putAvHashtable = putTotalHashtable / loops;
-        long getAvHashtable = getTotalHashtable / loops;
-
-        System.out.println( "Finished " + loops + " loops of " + tries + " gets and puts" );
-
-        System.out.println( "\n" );
-        System.out.println( "Put average for JCS       = " + putAvJCS );
-        System.out.println( "Put average for Hashtable = " + putAvHashtable );
-        ratioPut = Float.intBitsToFloat( (int) putAvJCS ) / Float.intBitsToFloat( (int) putAvHashtable );
-        System.out.println( "JCS puts took " + ratioPut + " times the Hashtable, the goal is <" + target + "x" );
-
-        System.out.println( "\n" );
-        System.out.println( "Get average for JCS       = " + getAvJCS );
-        System.out.println( "Get average for Hashtable = " + getAvHashtable );
-        ratioGet = Float.intBitsToFloat( (int) getAvJCS ) / Float.intBitsToFloat( (int) getAvHashtable );
-        System.out.println( "JCS gets took " + ratioGet + " times the Hashtable, the goal is <" + target + "x" );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/RemovalTestUtil.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/RemovalTestUtil.java
deleted file mode 100644
index 793b884..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/RemovalTestUtil.java
+++ /dev/null
@@ -1,130 +0,0 @@
-package org.apache.commons.jcs;
-
-import org.apache.commons.jcs.access.CacheAccess;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-
-/**
- * Simple methods to be run by active test suites that test removal.
- *
- */
-public class RemovalTestUtil
-    extends TestCase
-{
-    /**
-     * Constructor for the TestSimpleLoad object
-     *
-     * @param testName
-     *            Description of the Parameter
-     */
-    public RemovalTestUtil( String testName )
-    {
-        super( testName );
-    }
-
-    /**
-     * Adds elements in the range specified and then removes them using the
-     * categorical or substring removal method.
-     *
-     * @param start
-     * @param end
-     *
-     * @throws Exception
-     *                Description of the Exception
-     */
-    public void runTestPutThenRemoveCategorical( int start, int end )
-        throws Exception
-    {
-        CacheAccess<String, String> jcs = JCS.getInstance( "testCache1" );
-
-        for ( int i = start; i <= end; i++ )
-        {
-            jcs.put( i + ":key", "data" + i );
-        }
-
-        for ( int i = end; i >= start; i-- )
-        {
-            String res = jcs.get( i + ":key" );
-            assertNotNull( "[" + i + ":key] should not be null", res );
-        }
-
-        for ( int i = start; i <= end; i++ )
-        {
-            jcs.remove( i + ":" );
-            assertNull( jcs.get( i + ":key" ) );
-        }
-    }
-
-    /**
-     * Put items in the cache in this key range. Can be used to verify that
-     * concurrent operations are not effected by things like hierchical removal.
-     *
-     * @param start
-     *            int
-     * @param end
-     *            int
-     * @throws Exception
-     */
-    public void runPutInRange( int start, int end )
-        throws Exception
-    {
-        CacheAccess<String, String> jcs = JCS.getInstance( "testCache1" );
-
-        for ( int i = start; i <= end; i++ )
-        {
-            jcs.put( i + ":key", "data" + i );
-        }
-
-        for ( int i = end; i >= start; i-- )
-        {
-            String res = jcs.get( i + ":key" );
-            assertNotNull( "[" + i + ":key] should not be null", res );
-        }
-    }
-
-    /**
-     * Just get from start to end.
-     *
-     * @param start
-     *            int
-     * @param end
-     *            int
-     * @param check
-     *            boolean -- check to see if the items are in the cache.
-     * @throws Exception
-     */
-    public void runGetInRange( int start, int end, boolean check )
-        throws Exception
-    {
-        CacheAccess<String, String> jcs = JCS.getInstance( "testCache1" );
-
-        // don't care if they are found
-        for ( int i = end; i >= start; i-- )
-        {
-            String res = jcs.get( i + ":key" );
-            if ( check )
-            {
-                assertNotNull( "[" + i + ":key] should not be null", res );
-            }
-        }
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/TestLogConfigurationUtil.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/TestLogConfigurationUtil.java
deleted file mode 100644
index 9609718..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/TestLogConfigurationUtil.java
+++ /dev/null
@@ -1,85 +0,0 @@
-package org.apache.commons.jcs;
-
-import java.io.StringWriter;
-import java.util.logging.Handler;
-import java.util.logging.Level;
-import java.util.logging.LogRecord;
-import java.util.logging.Logger;
-
-import org.apache.commons.jcs.log.LogManager;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/** Utility for testing log messages. */
-public class TestLogConfigurationUtil
-{
-    /**
-     * Configures a logger for the given name. This allows us to check the log output.
-     * <p>
-     * @param stringWriter string writer
-     * @param loggerName logger name
-     */
-    public static void configureLogger( StringWriter stringWriter, String loggerName )
-    {
-        LogManager.setLogSystem("jul");
-        java.util.logging.LogManager.getLogManager().reset();
-        Logger rootLogger = java.util.logging.LogManager.getLogManager().getLogger("");
-
-        rootLogger.addHandler(new MockLogHandler(stringWriter));
-        rootLogger.setLevel(Level.FINE);
-    }
-
-    private static class MockLogHandler extends Handler
-    {
-        private StringWriter writer;
-
-        public MockLogHandler(StringWriter writer)
-        {
-            super();
-            this.writer = writer;
-        }
-
-        @Override
-        public void publish(LogRecord record)
-        {
-            StringBuilder sb = new StringBuilder();
-            sb.append(record.getMillis())
-              .append(" - ")
-              .append(record.getSourceClassName())
-              .append("#")
-              .append(record.getSourceMethodName())
-              .append(" - ")
-              .append(record.getMessage())
-              .append('\n');
-            writer.append(sb.toString());
-        }
-
-        @Override
-        public void flush()
-        {
-            writer.flush();
-        }
-
-        @Override
-        public void close() throws SecurityException
-        {
-        }
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/TestTCPLateralCache.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/TestTCPLateralCache.java
deleted file mode 100644
index c229df5..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/TestTCPLateralCache.java
+++ /dev/null
@@ -1,145 +0,0 @@
-package org.apache.commons.jcs;
-
-import org.apache.commons.jcs.access.CacheAccess;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.extensions.ActiveTestSuite;
-import junit.framework.Test;
-import junit.framework.TestCase;
-
-/**
- * Test which exercises the indexed disk cache. This one uses three different
- * regions for thre threads.
- *
- * @version $Id$
- */
-public class TestTCPLateralCache
-    extends TestCase
-{
-    /**
-     * Number of items to cache, twice the configured maxObjects for the memory
-     * cache regions.
-     */
-    private static int items = 200;
-
-    /**
-     * Constructor for the TestTCPLateralCache object.
-     *
-     * @param testName
-     */
-    public TestTCPLateralCache( String testName )
-    {
-        super( testName );
-    }
-
-    /**
-     * A unit test suite for JUnit
-     *
-     * @return The test suite
-     */
-    public static Test suite()
-    {
-        ActiveTestSuite suite = new ActiveTestSuite();
-
-        suite.addTest( new TestTCPLateralCache( "testTcpRegion1_no_receiver" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "testTcpRegion1" );
-            }
-        } );
-
-        //        suite.addTest( new TestTCPLateralCache( "testIndexedDiskCache2" )
-        //        {
-        //            public void runTest() throws Exception
-        //            {
-        //                this.runTestForRegion( "indexedRegion2" );
-        //            }
-        //        } );
-        //
-        //        suite.addTest( new TestTCPLateralCache( "testIndexedDiskCache3" )
-        //        {
-        //            public void runTest() throws Exception
-        //            {
-        //                this.runTestForRegion( "indexedRegion3" );
-        //            }
-        //        } );
-
-        return suite;
-    }
-
-    /**
-     * Test setup
-     */
-    @Override
-    public void setUp()
-    {
-        JCS.setConfigFilename( "/TestTCPLateralCache.ccf" );
-    }
-
-    /**
-     * Adds items to cache, gets them, and removes them. The item count is more
-     * than the size of the memory cache, so items should spool to disk.
-     *
-     * @param region
-     *            Name of the region to access
-     *
-     * @throws Exception
-     *                If an error occurs
-     */
-    public void runTestForRegion( String region )
-        throws Exception
-    {
-        CacheAccess<String, String> jcs = JCS.getInstance( region );
-
-        // Add items to cache
-
-        for ( int i = 0; i <= items; i++ )
-        {
-            jcs.put( i + ":key", region + " data " + i );
-        }
-
-        // Test that all items are in cache
-
-        for ( int i = 0; i <= items; i++ )
-        {
-            String value = jcs.get( i + ":key" );
-
-            assertEquals( region + " data " + i, value );
-        }
-
-        // Remove all the items
-
-        for ( int i = 0; i <= items; i++ )
-        {
-            jcs.remove( i + ":key" );
-        }
-
-        // Verify removal
-
-        for ( int i = 0; i <= items; i++ )
-        {
-            assertNull( "Removed key should be null: " + i + ":key", jcs.get( i + ":key" ) );
-        }
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/ZeroSizeCacheUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/ZeroSizeCacheUnitTest.java
deleted file mode 100644
index fc6d6be..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/ZeroSizeCacheUnitTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-package org.apache.commons.jcs;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.access.CacheAccess;
-
-/**
- *
- * @author Aaron Smuts
- *
- */
-public class ZeroSizeCacheUnitTest
-    extends TestCase
-{
-    /** number to get each loop */
-    private static int items = 20000;
-
-    /**
-     * Test setup
-     * <p>
-     * @throws Exception
-     */
-    @Override
-    public void setUp()
-        throws Exception
-    {
-        JCS.setConfigFilename( "/TestZeroSizeCache.ccf" );
-        JCS.getInstance( "testCache1" );
-    }
-
-    /**
-     * Verify that a 0 size cache does not result in errors. You should be able
-     * to disable a region this way.
-     * @throws Exception
-     *
-     */
-    public void testPutGetRemove()
-        throws Exception
-    {
-        CacheAccess<String, String> jcs = JCS.getInstance( "testCache1" );
-
-        for ( int i = 0; i <= items; i++ )
-        {
-            jcs.put( i + ":key", "data" + i );
-        }
-
-        // all the gets should be null
-        for ( int i = items; i >= 0; i-- )
-        {
-            String res = jcs.get( i + ":key" );
-            assertNull( "[" + i + ":key] should be null", res );
-        }
-
-        // test removal, should be no exceptions
-        jcs.remove( "300:key" );
-
-        // allow the shrinker to run
-        Thread.sleep( 500 );
-
-        // do it again.
-        for ( int i = 0; i <= items; i++ )
-        {
-            jcs.put( i + ":key", "data" + i );
-        }
-
-        for ( int i = items; i >= 0; i-- )
-        {
-            String res = jcs.get( i + ":key" );
-            assertNull( "[" + i + ":key] should be null", res );
-        }
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/access/CacheAccessUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/access/CacheAccessUnitTest.java
deleted file mode 100644
index 2a508c1..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/access/CacheAccessUnitTest.java
+++ /dev/null
@@ -1,366 +0,0 @@
-package org.apache.commons.jcs.access;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.exception.CacheException;
-import org.apache.commons.jcs.access.exception.ObjectExistsException;
-import org.apache.commons.jcs.engine.CompositeCacheAttributes;
-import org.apache.commons.jcs.engine.ElementAttributes;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheAttributes;
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-
-import junit.framework.TestCase;
-
-/**
- * Tests the methods of the cache access class.
- * <p>
- * @author Aaron Smuts
- */
-public class CacheAccessUnitTest
-    extends TestCase
-{
-    /**
-     * Verify that we get an object exists exception if the item is in the cache.
-     * @throws Exception
-     */
-    public void testPutSafe()
-        throws Exception
-    {
-        CacheAccess<String, String> access = JCS.getInstance( "test" );
-        assertNotNull( "We should have an access class", access );
-
-        String key = "mykey";
-        String value = "myvalue";
-
-        access.put( key, value );
-
-        String returnedValue1 = access.get( key );
-        assertEquals( "Wrong value returned.", value, returnedValue1 );
-
-        try
-        {
-            access.putSafe( key, "someothervalue" );
-            fail( "We should have received an exception since this key is already in the cache." );
-        }
-        catch ( CacheException e )
-        {
-            assertTrue( "Wrong type of exception.", e instanceof ObjectExistsException );
-            assertTrue( "Should have the key in the error message.", e.getMessage().indexOf( "[" + key + "]" ) != -1 );
-        }
-
-        String returnedValue2 = access.get( key );
-        assertEquals( "Wrong value returned.  Should still be the original.", value, returnedValue2 );
-    }
-
-    /**
-     * Try to put a null key and verify that we get an exception.
-     * @throws Exception
-     */
-    public void testPutNullKey()
-        throws Exception
-    {
-        CacheAccess<String, String> access = JCS.getInstance( "test" );
-        assertNotNull( "We should have an access class", access );
-
-        String key = null;
-        String value = "myvalue";
-
-        try
-        {
-            access.put( key, value );
-            fail( "Should not have been able to put a null key." );
-        }
-        catch ( CacheException e )
-        {
-            assertTrue( "Should have the word null in the error message.", e.getMessage().indexOf( "null" ) != -1 );
-        }
-    }
-
-    /**
-     * Try to put a null value and verify that we get an exception.
-     * @throws Exception
-     */
-    public void testPutNullValue()
-        throws Exception
-    {
-        CacheAccess<String, String> access = JCS.getInstance( "test" );
-        assertNotNull( "We should have an access class", access );
-
-        String key = "myKey";
-        String value = null;
-
-        try
-        {
-            access.put( key, value );
-            fail( "Should not have been able to put a null object." );
-        }
-        catch ( CacheException e )
-        {
-            assertTrue( "Should have the word null in the error message.", e.getMessage().indexOf( "null" ) != -1 );
-        }
-    }
-
-    /**
-     * Verify that elements that go in the region after this call take the new attributes.
-     * @throws Exception
-     */
-    public void testSetDefaultElementAttributes()
-        throws Exception
-    {
-        CacheAccess<String, String> access = JCS.getInstance( "test" );
-        assertNotNull( "We should have an access class", access );
-
-        long maxLife = 9876;
-        IElementAttributes attr = new ElementAttributes();
-        attr.setMaxLife(maxLife);
-
-        access.setDefaultElementAttributes( attr );
-
-        assertEquals( "Wrong element attributes.", attr.getMaxLife(), access.getDefaultElementAttributes()
-            .getMaxLife() );
-
-        String key = "mykey";
-        String value = "myvalue";
-
-        access.put( key, value );
-
-        ICacheElement<String, String> element = access.getCacheElement( key );
-
-        assertEquals( "Wrong max life.  Should have the new value.", maxLife, element.getElementAttributes()
-            .getMaxLife() );
-    }
-
-    /**
-     * Verify that getCacheElements returns the elements requested based on the key.
-     * @throws Exception
-     */
-    public void testGetCacheElements()
-        throws Exception
-    {
-        //SETUP
-        CacheAccess<String, String> access = JCS.getInstance( "test" );
-        assertNotNull( "We should have an access class", access );
-
-        String keyOne = "mykeyone";
-        String keyTwo = "mykeytwo";
-        String keyThree = "mykeythree";
-        String keyFour = "mykeyfour";
-        String valueOne = "myvalueone";
-        String valueTwo = "myvaluetwo";
-        String valueThree = "myvaluethree";
-        String valueFour = "myvaluefour";
-
-        access.put( keyOne, valueOne );
-        access.put( keyTwo, valueTwo );
-        access.put( keyThree, valueThree );
-
-        Set<String> input = new HashSet<>();
-        input.add( keyOne );
-        input.add( keyTwo );
-
-        //DO WORK
-        Map<String, ICacheElement<String, String>> result = access.getCacheElements( input );
-
-        //VERIFY
-        assertEquals( "map size", 2, result.size() );
-        ICacheElement<String, String> elementOne = result.get( keyOne );
-        assertEquals( "value one", keyOne, elementOne.getKey() );
-        assertEquals( "value one", valueOne, elementOne.getVal() );
-        ICacheElement<String, String> elementTwo = result.get( keyTwo );
-        assertEquals( "value two", keyTwo, elementTwo.getKey() );
-        assertEquals( "value two", valueTwo, elementTwo.getVal() );
-
-        assertNull(access.get(keyFour));
-        String suppliedValue1 = access.get(keyFour, () -> valueFour);
-        assertNotNull( "value four", suppliedValue1);
-        assertEquals( "value four", valueFour, suppliedValue1);
-        String suppliedValue2 = access.get(keyFour);
-        assertNotNull( "value four", suppliedValue2);
-        assertEquals( "value four", suppliedValue1, suppliedValue2);
-    }
-
-    /**
-     * Verify that we can get a region using the define region method.
-     * @throws Exception
-     */
-    public void testRegionDefiniton()
-        throws Exception
-    {
-        CacheAccess<String, String> access = JCS.getInstance( "test" );
-        assertNotNull( "We should have an access class", access );
-    }
-
-    /**
-     * Verify that we can get a region using the define region method with cache attributes.
-     * @throws Exception
-     */
-    public void testRegionDefinitonWithAttributes()
-        throws Exception
-    {
-        ICompositeCacheAttributes ca = new CompositeCacheAttributes();
-
-        long maxIdleTime = 8765;
-        ca.setMaxMemoryIdleTimeSeconds( maxIdleTime );
-
-        CacheAccess<String, String> access = JCS.getInstance( "testRegionDefinitonWithAttributes", ca );
-        assertNotNull( "We should have an access class", access );
-
-        ICompositeCacheAttributes ca2 = access.getCacheAttributes();
-        assertEquals( "Wrong idle time setting.", ca.getMaxMemoryIdleTimeSeconds(), ca2.getMaxMemoryIdleTimeSeconds() );
-    }
-
-    /**
-     * Verify that we can get a region using the define region method with cache attributes and
-     * element attributes.
-     * @throws Exception
-     */
-    public void testRegionDefinitonWithBothAttributes()
-        throws Exception
-    {
-        ICompositeCacheAttributes ca = new CompositeCacheAttributes();
-
-        long maxIdleTime = 8765;
-        ca.setMaxMemoryIdleTimeSeconds( maxIdleTime );
-
-        long maxLife = 9876;
-        IElementAttributes attr = new ElementAttributes();
-        attr.setMaxLife(maxLife);
-
-        CacheAccess<String, String> access = JCS.getInstance( "testRegionDefinitonWithAttributes", ca, attr );
-        assertNotNull( "We should have an access class", access );
-
-        ICompositeCacheAttributes ca2 = access.getCacheAttributes();
-        assertEquals( "Wrong idle time setting.", ca.getMaxMemoryIdleTimeSeconds(), ca2.getMaxMemoryIdleTimeSeconds() );
-    }
-
-    /**
-     * Verify we can get some matching elements..
-     * <p>
-     * @throws Exception
-     */
-    public void testGetMatching_Normal()
-        throws Exception
-    {
-        // SETUP
-        int maxMemorySize = 1000;
-        String keyprefix1 = "MyPrefix1";
-        String keyprefix2 = "MyPrefix2";
-        String memoryCacheClassName = "org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache";
-        ICompositeCacheAttributes cattr = new CompositeCacheAttributes();
-        cattr.setMemoryCacheName( memoryCacheClassName );
-        cattr.setMaxObjects( maxMemorySize );
-
-        long maxLife = 9876;
-        IElementAttributes attr = new ElementAttributes();
-        attr.setMaxLife(maxLife);
-
-        CacheAccess<String, Integer> access = JCS.getInstance( "testGetMatching_Normal", cattr, attr );
-
-        // DO WORK
-        int numToInsertPrefix1 = 10;
-        // insert with prefix1
-        for ( int i = 0; i < numToInsertPrefix1; i++ )
-        {
-            access.put( keyprefix1 + String.valueOf( i ), Integer.valueOf( i ) );
-        }
-
-        int numToInsertPrefix2 = 50;
-        // insert with prefix1
-        for ( int i = 0; i < numToInsertPrefix2; i++ )
-        {
-            access.put( keyprefix2 + String.valueOf( i ), Integer.valueOf( i ) );
-        }
-
-        Map<String, Integer> result1 = access.getMatching( keyprefix1 + ".+" );
-        Map<String, Integer> result2 = access.getMatching( keyprefix2 + "\\S+" );
-
-        // VERIFY
-        assertEquals( "Wrong number returned 1:", numToInsertPrefix1, result1.size() );
-        assertEquals( "Wrong number returned 2:", numToInsertPrefix2, result2.size() );
-        //System.out.println( result1 );
-
-        // verify that the elements are unwrapped
-        for (Map.Entry<String, Integer> entry : result1.entrySet())
-        {
-            Object value = entry.getValue();
-            assertFalse( "Should not be a cache element.", value instanceof ICacheElement );
-        }
-    }
-
-    /**
-     * Verify we can get some matching elements..
-     * <p>
-     * @throws Exception
-     */
-    public void testGetMatchingElements_Normal()
-        throws Exception
-    {
-        // SETUP
-        int maxMemorySize = 1000;
-        String keyprefix1 = "MyPrefix1";
-        String keyprefix2 = "MyPrefix2";
-        String memoryCacheClassName = "org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache";
-        ICompositeCacheAttributes cattr = new CompositeCacheAttributes();
-        cattr.setMemoryCacheName( memoryCacheClassName );
-        cattr.setMaxObjects( maxMemorySize );
-
-        long maxLife = 9876;
-        IElementAttributes attr = new ElementAttributes();
-        attr.setMaxLife(maxLife);
-
-        CacheAccess<String, Integer> access = JCS.getInstance( "testGetMatching_Normal", cattr, attr );
-
-        // DO WORK
-        int numToInsertPrefix1 = 10;
-        // insert with prefix1
-        for ( int i = 0; i < numToInsertPrefix1; i++ )
-        {
-            access.put( keyprefix1 + String.valueOf( i ), Integer.valueOf( i ) );
-        }
-
-        int numToInsertPrefix2 = 50;
-        // insert with prefix1
-        for ( int i = 0; i < numToInsertPrefix2; i++ )
-        {
-            access.put( keyprefix2 + String.valueOf( i ), Integer.valueOf( i ) );
-        }
-
-        Map<String, ICacheElement<String, Integer>> result1 = access.getMatchingCacheElements( keyprefix1 + "\\S+" );
-        Map<String, ICacheElement<String, Integer>> result2 = access.getMatchingCacheElements( keyprefix2 + ".+" );
-
-        // VERIFY
-        assertEquals( "Wrong number returned 1:", numToInsertPrefix1, result1.size() );
-        assertEquals( "Wrong number returned 2:", numToInsertPrefix2, result2.size() );
-        //System.out.println( result1 );
-
-        // verify that the elements are wrapped
-        for (Map.Entry<String, ICacheElement<String, Integer>> entry : result1.entrySet())
-        {
-            Object value = entry.getValue();
-            assertTrue( "Should be a cache element.", value instanceof ICacheElement );
-        }
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/access/GroupCacheAccessUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/access/GroupCacheAccessUnitTest.java
deleted file mode 100644
index 414b314..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/access/GroupCacheAccessUnitTest.java
+++ /dev/null
@@ -1,240 +0,0 @@
-package org.apache.commons.jcs.access;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.Set;
-
-import junit.framework.TestCase;
-
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.exception.CacheException;
-
-/**
- * Tests the methods of the group cache access class.
- * <p>
- * @author Aaron Smuts
- */
-public class GroupCacheAccessUnitTest
-    extends TestCase
-{
-    /**
-     * Verify that we can put and get an object
-     * @throws Exception
-     */
-    public void testPutAndGet()
-        throws Exception
-    {
-        GroupCacheAccess<String, String> access = JCS.getGroupCacheInstance( "test" );
-        assertNotNull( "We should have an access class", access );
-
-        String key = "mykey";
-        String group = "mygroup";
-        String value = "myvalue";
-
-        access.putInGroup(key, group, value);
-
-        String returnedValue1 = access.getFromGroup(key, group);
-        assertEquals( "Wrong value returned.", value, returnedValue1 );
-    }
-
-    /**
-     * Try to put a null key and verify that we get an exception.
-     * @throws Exception
-     */
-    public void testPutNullKey()
-        throws Exception
-    {
-        GroupCacheAccess<String, String> access = JCS.getGroupCacheInstance( "test" );
-        assertNotNull( "We should have an access class", access );
-
-        String key = null;
-        String group = "mygroup";
-        String value = "myvalue";
-
-        try
-        {
-            access.putInGroup(key, group, value);
-            fail( "Should not have been able to put a null key." );
-        }
-        catch ( CacheException e )
-        {
-            assertTrue( "Should have the word null in the error message.", e.getMessage().indexOf( "null" ) != -1 );
-        }
-    }
-
-    /**
-     * Try to put a null value and verify that we get an exception.
-     * @throws Exception
-     */
-    public void testPutNullValue()
-        throws Exception
-    {
-        GroupCacheAccess<String, String> access = JCS.getGroupCacheInstance( "test" );
-        assertNotNull( "We should have an access class", access );
-
-        String key = "myKey";
-        String group = "mygroup";
-        String value = null;
-
-        try
-        {
-            access.putInGroup(key, group, value);
-            fail( "Should not have been able to put a null object." );
-        }
-        catch ( CacheException e )
-        {
-            assertTrue( "Should have the word null in the error message.", e.getMessage().indexOf( "null" ) != -1 );
-        }
-    }
-
-    /**
-     * Verify that we can remove items from the cache
-     * @throws Exception
-     */
-    public void testRemove()
-        throws Exception
-    {
-        GroupCacheAccess<String, String> access = JCS.getGroupCacheInstance( "test" );
-        assertNotNull( "We should have an access class", access );
-
-        String key = "mykey";
-        String group = "mygroup";
-        String value = "myvalue";
-
-        for (int i = 0; i < 10; i++)
-        {
-            access.putInGroup(key + i, group, value + i);
-        }
-
-        // Make sure cache contains some data
-        for (int i = 0; i < 10; i++)
-        {
-            String returnedValue1 = access.getFromGroup(key + i, group);
-            assertEquals( "Wrong value returned.", value + i, returnedValue1 );
-        }
-
-        access.removeFromGroup(key + 0, group);
-
-        assertNull("Should not be in cache", access.getFromGroup(key + 0, group));
-
-        for (int i = 1; i < 10; i++)
-        {
-            String returnedValue1 = access.getFromGroup(key + i, group);
-            assertEquals( "Wrong value returned.", value + i, returnedValue1 );
-        }
-    }
-
-    /**
-     * Verify that we can invalidate the group
-     * @throws Exception
-     */
-    public void testInvalidate()
-        throws Exception
-    {
-        GroupCacheAccess<String, String> access = JCS.getGroupCacheInstance( "test" );
-        assertNotNull( "We should have an access class", access );
-
-        String key = "mykey";
-        String group = "mygroup";
-        String value = "myvalue";
-
-        for (int i = 0; i < 10; i++)
-        {
-            access.putInGroup(key + i, group + 0, value + i);
-        }
-
-        for (int i = 0; i < 10; i++)
-        {
-            access.putInGroup(key + i, group + 1, value + i);
-        }
-
-        // Make sure cache contains some data
-        for (int i = 0; i < 10; i++)
-        {
-            String returnedValue1 = access.getFromGroup(key + i, group + 0);
-            assertEquals( "Wrong value returned.", value + i, returnedValue1 );
-            String returnedValue2 = access.getFromGroup(key + i, group + 1);
-            assertEquals( "Wrong value returned.", value + i, returnedValue2 );
-        }
-
-        access.invalidateGroup(group + 0);
-
-        for (int i = 0; i < 10; i++)
-        {
-            assertNull("Should not be in cache", access.getFromGroup(key + i, group + 0));
-        }
-
-        for (int i = 0; i < 10; i++)
-        {
-            String returnedValue1 = access.getFromGroup(key + i, group + 1);
-            assertEquals( "Wrong value returned.", value + i, returnedValue1 );
-        }
-    }
-
-    /**
-     * Verify we can use the group cache.
-     * <p>
-     * @throws Exception
-     */
-    public void testGroupCache()
-        throws Exception
-    {
-        GroupCacheAccess<String, Integer> access = JCS.getGroupCacheInstance( "testGroup" );
-        String groupName1 = "testgroup1";
-        String groupName2 = "testgroup2";
-
-        Set<String> keys1 = access.getGroupKeys( groupName1 );
-        assertNotNull(keys1);
-        assertEquals(0, keys1.size());
-
-        Set<String> keys2 = access.getGroupKeys( groupName2 );
-        assertNotNull(keys2);
-        assertEquals(0, keys2.size());
-
-        // DO WORK
-        int numToInsertGroup1 = 10;
-        // insert with prefix1
-        for ( int i = 0; i < numToInsertGroup1; i++ )
-        {
-            access.putInGroup(String.valueOf( i ), groupName1, Integer.valueOf( i ) );
-        }
-
-        int numToInsertGroup2 = 50;
-        // insert with prefix1
-        for ( int i = 0; i < numToInsertGroup2; i++ )
-        {
-            access.putInGroup(String.valueOf( i ), groupName2, Integer.valueOf( i + 1 ) );
-        }
-
-        keys1 = access.getGroupKeys( groupName1 ); // Test for JCS-102
-        assertNotNull(keys1);
-        assertEquals("Wrong number returned 1:", 10, keys1.size());
-
-        keys2 = access.getGroupKeys( groupName2 );
-        assertNotNull(keys2);
-        assertEquals("Wrong number returned 2:", 50, keys2.size());
-
-        assertEquals(Integer.valueOf(5), access.getFromGroup("5", groupName1));
-        assertEquals(Integer.valueOf(6), access.getFromGroup("5", groupName2));
-
-        assertTrue(access.getGroupNames().contains(groupName1));
-        assertTrue(access.getGroupNames().contains(groupName2));
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/access/SystemPropertyUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/access/SystemPropertyUnitTest.java
deleted file mode 100644
index a301efd..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/access/SystemPropertyUnitTest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-package org.apache.commons.jcs.access;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.engine.control.CompositeCacheManager;
-import org.junit.FixMethodOrder;
-import org.junit.runners.MethodSorters;
-
-/**
- * This test is for the system property usage in configuration values.
- *
- * @author Aaron Smuts
- *
- */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class SystemPropertyUnitTest
-    extends TestCase
-{
-
-    /**
-     * Verify that we use a system property for a ${FOO} string in a value.
-     *
-     * @throws Exception
-     *
-     */
-    public void test1SystemPropertyInValueDelimiter()
-        throws Exception
-    {
-
-        int maxMemory = 1234;
-        System.getProperties().setProperty( "MY_SYSTEM_PROPERTY_DISK_DIR", "system_set" );
-        System.getProperties().setProperty( "MY_SYSTEM_PROPERTY_MAX_SIZE", String.valueOf( maxMemory ) );
-
-        JCS.setConfigFilename( "/TestSystemProperties.ccf" );
-
-        CacheAccess<String, String> cache = JCS.getInstance( "test1" );
-        assertEquals( "We should have used the system property for the memory size", maxMemory, cache
-            .getCacheAttributes().getMaxObjects() );
-
-        System.clearProperty("MY_SYSTEM_PROPERTY_DISK_DIR");
-        System.clearProperty("MY_SYSTEM_PROPERTY_MAX_SIZE");
-    }
-
-    /**
-     * Verify that we use a system property for a ${FOO} string in a value. We
-     * define a propety in the cache.ccf file, but we do not have it as a system
-     * property. The default value should be used, if one exists.
-     *
-     * @throws Exception
-     *
-     */
-    public void test2SystemPropertyMissingInValueDelimeter()
-        throws Exception
-    {
-        System.getProperties().setProperty( "MY_SYSTEM_PROPERTY_DISK_DIR", "system_set" );
-
-        CompositeCacheManager mgr = CompositeCacheManager.getUnconfiguredInstance();
-        mgr.configure( "/TestSystemProperties.ccf" );
-
-        CacheAccess<String, String> cache = JCS.getInstance( "missing" );
-        // TODO check against the actual default def
-        assertEquals( "We should have used the default property for the memory size", 100, cache.getCacheAttributes()
-            .getMaxObjects() );
-
-        System.clearProperty("MY_SYSTEM_PROPERTY_DISK_DIR");
-
-    }
-
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/access/TestCacheAccess.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/access/TestCacheAccess.java
deleted file mode 100644
index 7cd9bf7..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/access/TestCacheAccess.java
+++ /dev/null
@@ -1,957 +0,0 @@
-package org.apache.commons.jcs.access;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Random;
-import java.util.StringTokenizer;
-
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.exception.CacheException;
-import org.apache.commons.jcs.engine.ElementAttributes;
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-import org.apache.commons.jcs.engine.control.CompositeCacheManager;
-import org.apache.commons.jcs.engine.control.event.ElementEventHandlerMockImpl;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * Allows the user to run common cache commands from the command line for a test cache. This also
- * provide basic methods for use in unit tests.
- */
-public class TestCacheAccess
-{
-    /** log instance */
-    private static final Log log = LogManager.getLog( TestCacheAccess.class );
-
-    /** cache instance to use in testing */
-    private CacheAccess<String, String> cache_control = null;
-
-    /** cache instance to use in testing */
-    private GroupCacheAccess<String, String> group_cache_control = null;
-
-    /** do we use system.out.println to print out debug data? */
-    private static boolean isSysOut = false;
-
-    /** Construct and initialize the cachecontrol based on the config file. */
-    public TestCacheAccess()
-    {
-        this( "testCache1" );
-    }
-
-    /**
-     * @param regionName the name of the region.
-     */
-    public TestCacheAccess( String regionName )
-    {
-        try
-        {
-            cache_control = JCS.getInstance( regionName );
-            group_cache_control = JCS.getGroupCacheInstance( regionName );
-        }
-        catch ( Exception e )
-        {
-            log.error( "Problem getting cache instance", e );
-            p( e.toString() );
-        }
-    }
-
-    /**
-     * This is the main loop called by the main method.
-     */
-    public void runLoop()
-    {
-        try
-        {
-            // process user input till done
-            boolean notDone = true;
-            String message = null;
-            // wait to dispose
-            BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) );
-
-            help();
-
-            while ( notDone )
-            {
-                p( "enter command:" );
-
-                message = br.readLine();
-
-                if ( message == null || message.startsWith( "help" ) )
-                {
-                    help();
-                }
-                else if ( message.startsWith( "gc" ) )
-                {
-                    System.gc();
-                }
-                else if ( message.startsWith( "getAttributeNames" ) )
-                {
-                    long n_start = System.currentTimeMillis();
-                    String groupName = null;
-                    StringTokenizer toke = new StringTokenizer( message );
-                    int tcnt = 0;
-                    while ( toke.hasMoreElements() )
-                    {
-                        tcnt++;
-                        String t = (String) toke.nextElement();
-                        if ( tcnt == 2 )
-                        {
-                            groupName = t.trim();
-                        }
-                    }
-                    getAttributeNames( groupName );
-                    long n_end = System.currentTimeMillis();
-                    p( "---got attrNames for " + groupName + " in " + String.valueOf( n_end - n_start ) + " millis ---" );
-                }
-                else if ( message.startsWith( "shutDown" ) )
-                {
-                    CompositeCacheManager.getInstance().shutDown();
-                    //cache_control.dispose();
-                    notDone = false;
-                    //System.exit( -1 );
-                    return;
-                }
-                /////////////////////////////////////////////////////////////////////
-                // get multiple from a region
-                else if ( message.startsWith( "getm" ) )
-                {
-                    processGetMultiple( message );
-                }
-                else if ( message.startsWith( "getg" ) )
-                {
-                    processGetGroup( message );
-                }
-                else if ( message.startsWith( "getag" ) )
-                {
-                    processGetAutoGroup( message );
-                }
-                else if ( message.startsWith( "getMatching" ) )
-                {
-                    processGetMatching( message );
-                }
-                else if ( message.startsWith( "get" ) )
-                {
-                    processGet( message );
-                }
-                else if ( message.startsWith( "putg" ) )
-                {
-                    processPutGroup( message );
-                }
-                // put automatically
-                else if ( message.startsWith( "putag" ) )
-                {
-                    processPutAutoGroup( message );
-                }
-                else if ( message.startsWith( "putm" ) )
-                {
-                    String numS = message.substring( message.indexOf( " " ) + 1, message.length() );
-                    if ( numS == null )
-                    {
-                        p( "usage: putm numbertoput" );
-                    }
-                    else
-                    {
-                        int num = Integer.parseInt( numS.trim() );
-                        putMultiple( num );
-                    }
-                }
-                else if ( message.startsWith( "pute" ) )
-                {
-                    String numS = message.substring( message.indexOf( " " ) + 1, message.length() );
-                    if ( numS == null )
-                    {
-                        p( "usage: putme numbertoput" );
-                    }
-                    else
-                    {
-                        int num = Integer.parseInt( numS.trim() );
-                        long n_start = System.currentTimeMillis();
-                        for ( int n = 0; n < num; n++ )
-                        {
-                            IElementAttributes attrp = cache_control.getDefaultElementAttributes();
-                            ElementEventHandlerMockImpl hand = new ElementEventHandlerMockImpl();
-                            attrp.addElementEventHandler( hand );
-                            cache_control.put( "key" + n, "data" + n + " put from ta = junk", attrp );
-                        }
-                        long n_end = System.currentTimeMillis();
-                        p( "---put " + num + " in " + String.valueOf( n_end - n_start ) + " millis ---" );
-                    }
-                }
-                else if ( message.startsWith( "put" ) )
-                {
-                    processPut( message );
-                }
-                else if ( message.startsWith( "removem" ) )
-                {
-                    String numS = message.substring( message.indexOf( " " ) + 1, message.length() );
-                    if ( numS == null )
-                    {
-                        p( "usage: removem numbertoremove" );
-                    }
-                    else
-                    {
-                        int num = Integer.parseInt( numS.trim() );
-                        removeMultiple( num );
-                    }
-                }
-                else if ( message.startsWith( "removeall" ) )
-                {
-                    cache_control.clear();
-                    p( "removed all" );
-                }
-                else if ( message.startsWith( "remove" ) )
-                {
-                    String key = message.substring( message.indexOf( " " ) + 1, message.length() );
-                    cache_control.remove( key );
-                    p( "removed " + key );
-                }
-                else if ( message.startsWith( "deattr" ) )
-                {
-                    IElementAttributes ae = cache_control.getDefaultElementAttributes();
-                    p( "Default IElementAttributes " + ae );
-                }
-                else if ( message.startsWith( "cloneattr" ) )
-                {
-                    String numS = message.substring( message.indexOf( " " ) + 1, message.length() );
-                    if ( numS == null )
-                    {
-                        p( "usage: put numbertoput" );
-                    }
-                    else
-                    {
-                        int num = Integer.parseInt( numS.trim() );
-                        IElementAttributes attrp = new ElementAttributes();
-                        long n_start = System.currentTimeMillis();
-                        for ( int n = 0; n < num; n++ )
-                        {
-                            attrp.clone();
-                        }
-                        long n_end = System.currentTimeMillis();
-                        p( "---cloned attr " + num + " in " + String.valueOf( n_end - n_start ) + " millis ---" );
-                    }
-                }
-                else if ( message.startsWith( "switch" ) )
-                {
-                    String name = message.substring( message.indexOf( " " ) + 1, message.length() );
-
-                    setRegion( name );
-                    p( "switched to cache = " + name );
-                    p( cache_control.toString() );
-                }
-                else if ( message.startsWith( "stats" ) )
-                {
-                    p( cache_control.getStats() );
-                }
-                else if ( message.startsWith( "gc" ) )
-                {
-                    System.gc();
-                    p( "Called system.gc()" );
-                }
-                else if ( message.startsWith( "random" ) )
-                {
-                    processRandom( message );
-                }
-            }
-        }
-        catch ( CacheException | IOException e )
-        {
-            p( e.toString() );
-            e.printStackTrace( System.out );
-        }
-    }
-
-    /**
-     * @param message
-     */
-    private void processGetMultiple( String message )
-    {
-        int num = 0;
-        boolean show = true;
-
-        StringTokenizer toke = new StringTokenizer( message );
-        int tcnt = 0;
-        while ( toke.hasMoreElements() )
-        {
-            tcnt++;
-            String t = (String) toke.nextElement();
-            if ( tcnt == 2 )
-            {
-                try
-                {
-                    num = Integer.parseInt( t.trim() );
-                }
-                catch ( NumberFormatException nfe )
-                {
-                    p( t + "not a number" );
-                }
-            }
-            else if ( tcnt == 3 )
-            {
-                show = Boolean.valueOf( t ).booleanValue();
-            }
-        }
-
-        if ( tcnt < 2 )
-        {
-            p( "usage: get numbertoget show values[true|false]" );
-        }
-        else
-        {
-            getMultiple( num, show );
-        }
-    }
-
-    /**
-     * @param message
-     */
-    private void processGetGroup( String message )
-    {
-        String key = null;
-        String group = null;
-        boolean show = true;
-
-        StringTokenizer toke = new StringTokenizer( message );
-        int tcnt = 0;
-        while ( toke.hasMoreElements() )
-        {
-            tcnt++;
-            String t = (String) toke.nextElement();
-            if ( tcnt == 2 )
-            {
-                key = t.trim();
-            }
-            else if ( tcnt == 3 )
-            {
-                group = t.trim();
-            }
-            else if ( tcnt == 4 )
-            {
-                show = Boolean.valueOf( t ).booleanValue();
-            }
-        }
-
-        if ( tcnt < 2 )
-        {
-            p( "usage: get key show values[true|false]" );
-        }
-        else
-        {
-            long n_start = System.currentTimeMillis();
-            try
-            {
-                Object obj = group_cache_control.getFromGroup( key, group );
-                if ( show && obj != null )
-                {
-                    p( obj.toString() );
-                }
-            }
-            catch ( Exception e )
-            {
-                log.error( e );
-            }
-            long n_end = System.currentTimeMillis();
-            p( "---got " + key + " from group " + group + " in " + String.valueOf( n_end - n_start ) + " millis ---" );
-        }
-    }
-
-    /**
-     * @param message
-     */
-    private void processGetAutoGroup( String message )
-    {
-        // get auto from group
-
-        int num = 0;
-        String group = null;
-        boolean show = true;
-
-        StringTokenizer toke = new StringTokenizer( message );
-        int tcnt = 0;
-        while ( toke.hasMoreElements() )
-        {
-            tcnt++;
-            String t = (String) toke.nextElement();
-            if ( tcnt == 2 )
-            {
-                num = Integer.parseInt( t.trim() );
-            }
-            else if ( tcnt == 3 )
-            {
-                group = t.trim();
-            }
-            else if ( tcnt == 4 )
-            {
-                show = Boolean.valueOf( t ).booleanValue();
-            }
-        }
-
-        if ( tcnt < 2 )
-        {
-            p( "usage: get key show values[true|false]" );
-        }
-        else
-        {
-            long n_start = System.currentTimeMillis();
-            try
-            {
-                for ( int a = 0; a < num; a++ )
-                {
-                    Object obj = group_cache_control.getFromGroup( "keygr" + a, group );
-                    if ( show && obj != null )
-                    {
-                        p( obj.toString() );
-                    }
-                }
-            }
-            catch ( Exception e )
-            {
-                log.error( e );
-            }
-            long n_end = System.currentTimeMillis();
-            p( "---got " + num + " from group " + group + " in " + String.valueOf( n_end - n_start ) + " millis ---" );
-        }
-    }
-
-    /**
-     * @param message
-     * @throws CacheException
-     */
-    private void processPutGroup( String message )
-        throws CacheException
-    {
-        String group = null;
-        String key = null;
-        StringTokenizer toke = new StringTokenizer( message );
-        int tcnt = 0;
-        while ( toke.hasMoreElements() )
-        {
-            tcnt++;
-            String t = (String) toke.nextElement();
-            if ( tcnt == 2 )
-            {
-                key = t.trim();
-            }
-            else if ( tcnt == 3 )
-            {
-                group = t.trim();
-            }
-        }
-
-        if ( tcnt < 3 )
-        {
-            p( "usage: putg key group" );
-        }
-        else
-        {
-            long n_start = System.currentTimeMillis();
-            group_cache_control.putInGroup( key, group, "data from putg ----asdfasfas-asfasfas-asfas in group " + group );
-            long n_end = System.currentTimeMillis();
-            p( "---put " + key + " in group " + group + " in " + String.valueOf( n_end - n_start ) + " millis ---" );
-        }
-    }
-
-    /**
-     * @param message
-     * @throws CacheException
-     */
-    private void processPutAutoGroup( String message )
-        throws CacheException
-    {
-        String group = null;
-        int num = 0;
-        StringTokenizer toke = new StringTokenizer( message );
-        int tcnt = 0;
-        while ( toke.hasMoreElements() )
-        {
-            tcnt++;
-            String t = (String) toke.nextElement();
-            if ( tcnt == 2 )
-            {
-                num = Integer.parseInt( t.trim() );
-            }
-            else if ( tcnt == 3 )
-            {
-                group = t.trim();
-            }
-        }
-
-        if ( tcnt < 3 )
-        {
-            p( "usage: putag num group" );
-        }
-        else
-        {
-            long n_start = System.currentTimeMillis();
-            for ( int a = 0; a < num; a++ )
-            {
-                group_cache_control.putInGroup( "keygr" + a, group, "data " + a
-                    + " from putag ----asdfasfas-asfasfas-asfas in group " + group );
-            }
-            long n_end = System.currentTimeMillis();
-            p( "---put " + num + " in group " + group + " in " + String.valueOf( n_end - n_start ) + " millis ---" );
-        }
-    }
-
-    /**
-     * @param message
-     * @throws CacheException
-     */
-    private void processPut( String message )
-        throws CacheException
-    {
-        String key = null;
-        String val = null;
-        StringTokenizer toke = new StringTokenizer( message );
-        int tcnt = 0;
-        while ( toke.hasMoreElements() )
-        {
-            tcnt++;
-            String t = (String) toke.nextElement();
-            if ( tcnt == 2 )
-            {
-                key = t.trim();
-            }
-            else if ( tcnt == 3 )
-            {
-                val = t.trim();
-            }
-        }
-
-        if ( tcnt < 3 )
-        {
-            p( "usage: put key val" );
-        }
-        else
-        {
-
-            long n_start = System.currentTimeMillis();
-            cache_control.put( key, val );
-            long n_end = System.currentTimeMillis();
-            p( "---put " + key + " | " + val + " in " + String.valueOf( n_end - n_start ) + " millis ---" );
-        }
-    }
-
-    /**
-     * @param message
-     */
-    private void processRandom( String message )
-    {
-        String rangeS = "";
-        String numOpsS = "";
-        boolean show = true;
-
-        StringTokenizer toke = new StringTokenizer( message );
-        int tcnt = 0;
-        while ( toke.hasMoreElements() )
-        {
-            tcnt++;
-            String t = (String) toke.nextElement();
-            if ( tcnt == 2 )
-            {
-                rangeS = t.trim();
-            }
-            else if ( tcnt == 3 )
-            {
-                numOpsS = t.trim();
-            }
-            else if ( tcnt == 4 )
-            {
-                show = Boolean.valueOf( t ).booleanValue();
-            }
-        }
-
-        String numS = message.substring( message.indexOf( " " ) + 1, message.length() );
-
-        int range = 0;
-        int numOps = 0;
-        try
-        {
-            range = Integer.parseInt( rangeS.trim() );
-            numOps = Integer.parseInt( numOpsS.trim() );
-        }
-        catch ( Exception e )
-        {
-            p( "usage: random range numOps show" );
-            p( "ex.  random 100 1000 false" );
-        }
-        if ( numS == null )
-        {
-            p( "usage: random range numOps show" );
-            p( "ex.  random 100 1000 false" );
-        }
-        else
-        {
-            random( range, numOps, show );
-        }
-    }
-
-    /**
-     * @param message
-     */
-    private void processGet( String message )
-    {
-        // plain old get
-
-        String key = null;
-        boolean show = true;
-
-        StringTokenizer toke = new StringTokenizer( message );
-        int tcnt = 0;
-        while ( toke.hasMoreElements() )
-        {
-            tcnt++;
-            String t = (String) toke.nextElement();
-            if ( tcnt == 2 )
-            {
-                key = t.trim();
-            }
-            else if ( tcnt == 3 )
-            {
-                show = Boolean.valueOf( t ).booleanValue();
-            }
-        }
-
-        if ( tcnt < 2 )
-        {
-            p( "usage: get key show values[true|false]" );
-        }
-        else
-        {
-            long n_start = System.currentTimeMillis();
-            try
-            {
-                Object obj = cache_control.get( key );
-                if ( show && obj != null )
-                {
-                    p( obj.toString() );
-                }
-            }
-            catch ( Exception e )
-            {
-                log.error( e );
-            }
-            long n_end = System.currentTimeMillis();
-            p( "---got " + key + " in " + String.valueOf( n_end - n_start ) + " millis ---" );
-        }
-    }
-
-    /**
-     * @param message
-     */
-    private void processGetMatching( String message )
-    {
-        // plain old get
-
-        String pattern = null;
-        boolean show = true;
-
-        StringTokenizer toke = new StringTokenizer( message );
-        int tcnt = 0;
-        while ( toke.hasMoreElements() )
-        {
-            tcnt++;
-            String t = (String) toke.nextElement();
-            if ( tcnt == 2 )
-            {
-                pattern = t.trim();
-            }
-            else if ( tcnt == 3 )
-            {
-                show = Boolean.valueOf( t ).booleanValue();
-            }
-        }
-
-        if ( tcnt < 2 )
-        {
-            p( "usage: getMatching key show values[true|false]" );
-        }
-        else
-        {
-            long n_start = System.currentTimeMillis();
-            try
-            {
-                Map<String, String> results = cache_control.getMatching( pattern );
-                if ( show && results != null )
-                {
-                    p( results.toString() );
-                }
-            }
-            catch ( Exception e )
-            {
-                log.error( e );
-            }
-            long n_end = System.currentTimeMillis();
-            p( "---gotMatching [" + pattern + "] in " + String.valueOf( n_end - n_start ) + " millis ---" );
-        }
-    }
-
-    /**
-     * Test harness.
-     * @param args The command line arguments
-     */
-    public static void main( String[] args )
-    {
-        isSysOut = true;
-        String ccfFileName = args[0];
-        if ( ccfFileName != null )
-        {
-            JCS.setConfigFilename( ccfFileName );
-        }
-        TestCacheAccess tca = new TestCacheAccess( "testCache1" );
-        tca.runLoop();
-    }
-
-    // end main
-    /////////////////////////////////////////////////////////////////////////////
-
-    /**
-     * Gets multiple items from the cache with keys of the form key1, key2, key3 up to key[num].
-     * @param num int
-     */
-    public void getMultiple( int num )
-    {
-        getMultiple( num, false );
-    }
-
-    /**
-     * @param num
-     * @param show
-     */
-    public void getMultiple( int num, boolean show )
-    {
-        long n_start = System.currentTimeMillis();
-        for ( int n = 0; n < num; n++ )
-        {
-            try
-            {
-                Object obj = cache_control.get( "key" + n );
-                if ( show && obj != null )
-                {
-                    p( obj.toString() );
-                }
-            }
-            catch ( Exception e )
-            {
-                log.error( e );
-            }
-        }
-        long n_end = System.currentTimeMillis();
-        p( "---got " + num + " in " + String.valueOf( n_end - n_start ) + " millis ---" );
-    }
-
-    /**
-     * Puts multiple items into the cache.
-     * @param num int
-     */
-    public void putMultiple( int num )
-    {
-        try
-        {
-            long n_start = System.currentTimeMillis();
-            for ( int n = 0; n < num; n++ )
-            {
-                cache_control.put( "key" + n, "data" + n + " put from ta = junk" );
-            }
-            long n_end = System.currentTimeMillis();
-            p( "---put " + num + " in " + String.valueOf( n_end - n_start ) + " millis ---" );
-        }
-        catch ( Exception e )
-        {
-            log.error( e );
-        }
-    }
-
-    /**
-     * Removes multiple items from the cache.
-     * @param num int
-     */
-    public void removeMultiple( int num )
-    {
-        try
-        {
-            long n_start = System.currentTimeMillis();
-            for ( int n = 0; n < num; n++ )
-            {
-                cache_control.remove( "key" + n );
-            }
-            long n_end = System.currentTimeMillis();
-            p( "---removed " + num + " in " + String.valueOf( n_end - n_start ) + " millis ---" );
-        }
-        catch ( Exception e )
-        {
-            log.error( e );
-        }
-    }
-
-    /**
-     * The random method performs numOps number of operations. The operations will be a mix of puts,
-     * gets, and removes. The key range will be from 0 to range.
-     * @param range int The end of the key range.
-     * @param numOps int The number of operations to perform
-     */
-    public void random( int range, int numOps )
-    {
-        random( range, numOps, false );
-    }
-
-    /**
-     * @param range
-     * @param numOps
-     * @param show
-     */
-    public void random( int range, int numOps, boolean show )
-    {
-        try
-        {
-            for ( int i = 1; i < numOps; i++ )
-            {
-                Random ran = new Random( i );
-                int n = ran.nextInt( 4 );
-                int kn = ran.nextInt( range );
-                String key = "key" + kn;
-                if ( n == 1 )
-                {
-                    cache_control.put( key, "data" + i + " junk asdfffffffadfasdfasf " + kn + ":" + n );
-                    if ( show )
-                    {
-                        p( "put " + key );
-                    }
-                }
-                else if ( n == 2 )
-                {
-                    cache_control.remove( key );
-                    if ( show )
-                    {
-                        p( "removed " + key );
-                    }
-                }
-                else
-                {
-                    // slightly greater chance of get
-                    Object obj = cache_control.get( key );
-                    if ( show && obj != null )
-                    {
-                        p( obj.toString() );
-                    }
-                }
-
-                if ( i % 10000 == 0 )
-                {
-                    p( cache_control.getStats() );
-                }
-
-            }
-            p( "Finished random cycle of " + numOps );
-        }
-        catch ( Exception e )
-        {
-            p( e.toString() );
-            e.printStackTrace( System.out );
-        }
-    }
-
-    /**
-     * Sets the region to be used by test methods.
-     * @param name String -- Name of region
-     */
-    public void setRegion( String name )
-    {
-        try
-        {
-            cache_control = JCS.getInstance( name );
-        }
-        catch ( Exception e )
-        {
-            p( e.toString() );
-            e.printStackTrace( System.out );
-        }
-
-    }
-
-    /////////////////////////////////////////////////////////////////////////////
-    /**
-     * The tester will print to the console if isSysOut is true, else it will log. It is false by
-     * default. When run via the main method, isSysOut will be set to true
-     * @param s String to print or log
-     */
-    public static void p( String s )
-    {
-        if ( isSysOut )
-        {
-            System.out.println( s );
-        }
-        else
-        {
-            if ( log.isDebugEnabled() )
-            {
-                log.debug( s );
-            }
-        }
-    }
-
-    /**
-     * Displays usage information for command line testing.
-     */
-    public static void help()
-    {
-        p( "\n\n\n\n" );
-        p( "type 'shutDown' to shutdown the cache" );
-        p( "type 'getm num show[false|true]' to get num automatically from a region" );
-        p( "type 'putm num' to put num automatically to a region" );
-        p( "type 'removeall' to remove all items in a region" );
-        p( "type 'remove key' to remove" );
-        p( "type 'removem num' to remove a number automatically" );
-        p( "type 'getMatching pattern show' to getMatching" );
-        p( "type 'get key show' to get" );
-        p( "type 'getg key group show' to get" );
-        p( "type 'getag num group show' to get automatically from a group" );
-        p( "type 'getAttributeNames group' to get a list og the group elements" );
-        p( "type 'putg key group val' to put" );
-        p( "type 'putag num group' to put automatically from a group" );
-        p( "type 'put key val' to put" );
-        p( "type 'stats' to get stats" );
-        p( "type 'deattr' to get the default element attributes" );
-        p( "type 'cloneattr num' to clone attr" );
-        p( "type 'random range numOps' to put, get, and remove randomly" );
-        p( "type 'switch name' to switch to this region name" );
-        p( "type 'gc' to call System.gc()" );
-        p( "type 'help' for commands" );
-
-    }
-
-    /**
-     * Gets the attributeNames attribute of the TestCacheAccess class
-     * @param groupName
-     */
-    public void getAttributeNames( String groupName )
-    {
-        Iterator<String> iter = group_cache_control.getGroupKeys( groupName ).iterator();
-
-        while ( iter.hasNext() )
-        {
-            p( "=" + iter.next() );
-        }
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/admin/AdminBeanUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/admin/AdminBeanUnitTest.java
deleted file mode 100644
index 38a75ff..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/admin/AdminBeanUnitTest.java
+++ /dev/null
@@ -1,154 +0,0 @@
-package org.apache.commons.jcs.admin;
-
-import java.util.List;
-
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-
-/**
- * Test the admin bean that is used by the JCSAdmin.jsp
- *
- * @author Aaron Smuts
- *
- */
-public class AdminBeanUnitTest
-    extends TestCase
-{
-
-    /**
-     * Create a test region and then verify that we get it from the list.
-     *
-     * @throws Exception
-     *
-     */
-    public void testGetRegionInfo()
-        throws Exception
-    {
-        String regionName = "myRegion";
-        CacheAccess<String, String> cache = JCS.getInstance( regionName );
-
-        cache.put( "key", "value" );
-
-        JCSAdminBean admin = new JCSAdminBean();
-
-        List<CacheRegionInfo> regions = admin.buildCacheInfo();
-
-        boolean foundRegion = false;
-
-        for (CacheRegionInfo info : regions)
-        {
-
-            if ( info.getCacheName().equals( regionName ) )
-            {
-                foundRegion = true;
-
-                assertTrue( "Byte count should be greater than 5.", info.getByteCount() > 5 );
-
-                assertNotNull( "Should have stats.", info.getCacheStatistics() );
-            }
-        }
-
-        assertTrue( "Should have found the region we just created.", foundRegion );
-    }
-
-    /**
-     * Put a value in a region and verify that it shows up.
-     *
-     * @throws Exception
-     */
-    public void testGetElementForRegionInfo()
-        throws Exception
-    {
-        String regionName = "myRegion";
-        CacheAccess<String, String> cache = JCS.getInstance( regionName );
-
-        // clear the region
-        cache.clear();
-
-        String key = "myKey";
-        cache.put( key, "value" );
-
-        JCSAdminBean admin = new JCSAdminBean();
-
-        List<CacheElementInfo> elements = admin.buildElementInfo( regionName );
-        assertEquals( "Wrong number of elements in the region.", 1, elements.size() );
-
-        CacheElementInfo elementInfo = elements.get(0);
-        assertEquals( "Wrong key." + elementInfo, key, elementInfo.getKey() );
-    }
-
-    /**
-     * Remove an item via the remove method.
-     *
-     * @throws Exception
-     */
-    public void testRemove()
-        throws Exception
-    {
-        JCSAdminBean admin = new JCSAdminBean();
-
-        String regionName = "myRegion";
-        CacheAccess<String, String> cache = JCS.getInstance( regionName );
-
-        // clear the region
-        cache.clear();
-        admin.clearRegion( regionName );
-
-        String key = "myKey";
-        cache.put( key, "value" );
-
-        List<CacheElementInfo> elements = admin.buildElementInfo( regionName );
-        assertEquals( "Wrong number of elements in the region.", 1, elements.size() );
-
-        CacheElementInfo elementInfo = elements.get(0);
-        assertEquals( "Wrong key.", key, elementInfo.getKey() );
-
-        admin.removeItem( regionName, key );
-
-        List<CacheElementInfo> elements2 = admin.buildElementInfo( regionName );
-        assertEquals( "Wrong number of elements in the region after remove.", 0, elements2.size() );
-    }
-
-    /**
-     * Add an item to a region. Call clear all and verify that it doesn't exist.
-     *
-     * @throws Exception
-     */
-    public void testClearAll()
-        throws Exception
-    {
-        JCSAdminBean admin = new JCSAdminBean();
-
-        String regionName = "myRegion";
-        CacheAccess<String, String> cache = JCS.getInstance( regionName );
-
-        String key = "myKey";
-        cache.put( key, "value" );
-
-        admin.clearAllRegions();
-
-        List<CacheElementInfo> elements2 = admin.buildElementInfo( regionName );
-        assertEquals( "Wrong number of elements in the region after remove.", 0, elements2.size() );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/admin/CountingStreamUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/admin/CountingStreamUnitTest.java
deleted file mode 100644
index 129575b..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/admin/CountingStreamUnitTest.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package org.apache.commons.jcs.admin;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-
-/**
- * Tests for the counting only output stream.
- *
- * @author Aaron Smuts
- *
- */
-public class CountingStreamUnitTest
-    extends TestCase
-{
-
-    /**
-     * Write a single byte and verify the count.
-     *
-     * @throws Exception
-     */
-    public void testSingleByte() throws Exception
-    {
-        CountingOnlyOutputStream out = new CountingOnlyOutputStream();
-        out.write( 1 );
-        assertEquals( "Wrong number of bytes written.", 1, out.getCount() );
-        out.write( 1 );
-        assertEquals( "Wrong number of bytes written.", 2, out.getCount() );
-        out.close();
-    }
-
-    /**
-     * This should count the size of the array.
-     *
-     * @throws Exception
-     */
-    public void testByteArray() throws Exception
-    {
-        CountingOnlyOutputStream out = new CountingOnlyOutputStream();
-        byte[] array = new byte[]{1,2,3,4,5};
-        out.write( array );
-        assertEquals( "Wrong number of bytes written.", array.length, out.getCount() );
-        out.close();
-    }
-
-    /**
-     * This should count the len -- the third arg
-     *
-     * @throws Exception
-     */
-    public void testByteArrayLenCount() throws Exception
-    {
-        CountingOnlyOutputStream out = new CountingOnlyOutputStream();
-        byte[] array = new byte[]{1,2,3,4,5};
-        int len = 3;
-        out.write( array, 0, len );
-        assertEquals( "Wrong number of bytes written.", len, out.getCount() );
-        out.close();
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/admin/TestJMX.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/admin/TestJMX.java
deleted file mode 100644
index 5a7b3ba..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/admin/TestJMX.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package org.apache.commons.jcs.admin;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-
-/**
- * Helper class to test the JMX registration
- */
-public class TestJMX
-{
-	public static void main(String[] args) throws Exception
-	{
-		CacheAccess<String, String> cache = JCS.getInstance("test");
-
-		cache.put("key", "value");
-        System.out.println("Waiting...");
-        Thread.sleep(Long.MAX_VALUE);
-	}
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/AuxiliaryCacheConfiguratorUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/AuxiliaryCacheConfiguratorUnitTest.java
deleted file mode 100644
index 685e3e6..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/AuxiliaryCacheConfiguratorUnitTest.java
+++ /dev/null
@@ -1,129 +0,0 @@
-package org.apache.commons.jcs.auxiliary;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.engine.control.MockElementSerializer;
-import org.apache.commons.jcs.engine.logging.MockCacheEventLogger;
-import org.apache.commons.jcs.utils.serialization.StandardSerializer;
-
-import java.util.Properties;
-
-/** Unit tests for the auxiliary cache configurator. */
-public class AuxiliaryCacheConfiguratorUnitTest
-    extends TestCase
-{
-    /**
-     * Verify that we don't get an error.
-     */
-    public void testParseCacheEventLogger_Null()
-    {
-        // SETUP
-        Properties props = new Properties();
-
-        // DO WORK
-        MockCacheEventLogger result = (MockCacheEventLogger) AuxiliaryCacheConfigurator.parseCacheEventLogger( props,
-                                                                                                               "junk" );
-
-        // VERIFY
-        assertNull( "Should not have a logger.", result );
-    }
-
-    /**
-     * Verify that we don't get an error.
-     */
-    public void testParseCacheEventLogger_NullName()
-    {
-        // SETUP
-        Properties props = new Properties();
-
-        // DO WORK
-        MockCacheEventLogger result = (MockCacheEventLogger) AuxiliaryCacheConfigurator.parseCacheEventLogger( props,
-                                                                                                               null );
-
-        // VERIFY
-        assertNull( "Should not have a logger.", result );
-    }
-
-    /**
-     * Verify that we can parse the event logger.
-     */
-    public void testParseCacheEventLogger_Normal()
-    {
-        // SETUP
-        String auxPrefix = "jcs.auxiliary." + "MYAux";
-        String testPropertyValue = "This is the value";
-        String className = MockCacheEventLogger.class.getName();
-
-        Properties props = new Properties();
-        props.put( auxPrefix + AuxiliaryCacheConfigurator.CACHE_EVENT_LOGGER_PREFIX, className );
-        props.put( auxPrefix + AuxiliaryCacheConfigurator.CACHE_EVENT_LOGGER_PREFIX
-            + AuxiliaryCacheConfigurator.ATTRIBUTE_PREFIX + ".testProperty", testPropertyValue );
-
-        // DO WORK
-        MockCacheEventLogger result = (MockCacheEventLogger) AuxiliaryCacheConfigurator
-            .parseCacheEventLogger( props, auxPrefix );
-
-        // VERIFY
-        assertNotNull( "Should have a logger.", result );
-        assertEquals( "Property should be set.", testPropertyValue, result.getTestProperty() );
-    }
-
-    /**
-     * Verify that we can parse the ElementSerializer.
-     */
-    public void testParseElementSerializer_Normal()
-    {
-        // SETUP
-        String auxPrefix = "jcs.auxiliary." + "MYAux";
-        String testPropertyValue = "This is the value";
-        String className = MockElementSerializer.class.getName();
-
-        Properties props = new Properties();
-        props.put( auxPrefix + AuxiliaryCacheConfigurator.SERIALIZER_PREFIX, className );
-        props.put( auxPrefix + AuxiliaryCacheConfigurator.SERIALIZER_PREFIX
-            + AuxiliaryCacheConfigurator.ATTRIBUTE_PREFIX + ".testProperty", testPropertyValue );
-
-        // DO WORK
-        MockElementSerializer result = (MockElementSerializer) AuxiliaryCacheConfigurator
-            .parseElementSerializer( props, auxPrefix );
-
-        // VERIFY
-        assertNotNull( "Should have a Serializer.", result );
-        assertEquals( "Property should be set.", testPropertyValue, result.getTestProperty() );
-    }
-
-    /**
-     * Verify that we can parse the ElementSerializer.
-     */
-    public void testParseElementSerializer_Null()
-    {
-        // SETUP
-        Properties props = new Properties();
-
-        // DO WORK
-        IElementSerializer result = AuxiliaryCacheConfigurator
-            .parseElementSerializer( props, "junk" );
-
-        // VERIFY
-        assertTrue( "Should have the default Serializer.", result instanceof StandardSerializer );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/MockAuxiliaryCache.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/MockAuxiliaryCache.java
deleted file mode 100644
index f17d430..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/MockAuxiliaryCache.java
+++ /dev/null
@@ -1,215 +0,0 @@
-package org.apache.commons.jcs.auxiliary;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.commons.jcs.engine.CacheStatus;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.stats.behavior.IStats;
-
-/**
- * Mock auxiliary for unit tests.
- * <p>
- * @author Aaron Smuts
- */
-public class MockAuxiliaryCache<K, V>
-    extends AbstractAuxiliaryCache<K, V>
-{
-    /** Can setup the cache type */
-    public CacheType cacheType = CacheType.DISK_CACHE;
-
-    /** Can setup status */
-    public CacheStatus status = CacheStatus.ALIVE;
-
-    /** Times getMatching was Called */
-    public int getMatchingCallCount = 0;
-
-    /**
-     * @param ce
-     * @throws IOException
-     */
-    @Override
-    public void update( ICacheElement<K, V> ce )
-        throws IOException
-    {
-        // TODO Auto-generated method stub
-
-    }
-
-    /**
-     * @param key
-     * @return ICacheElement
-     * @throws IOException
-     */
-    @Override
-    public ICacheElement<K, V> get( K key )
-        throws IOException
-    {
-        // TODO Auto-generated method stub
-        return null;
-    }
-
-    /**
-     * @param pattern
-     * @return Map
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMatching(String pattern)
-        throws IOException
-    {
-        getMatchingCallCount++;
-        return new HashMap<>();
-    }
-
-    /**
-     * Gets multiple items from the cache based on the given set of keys.
-     * <p>
-     * @param keys
-     * @return a map of K key to ICacheElement&lt;String, String&gt; element, or an empty map if there is no
-     *         data in cache for any of these keys
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMultiple(Set<K> keys)
-    {
-        return new HashMap<>();
-    }
-
-    /**
-     * @param key
-     * @return boolean
-     * @throws IOException
-     */
-    @Override
-    public boolean remove( K key )
-        throws IOException
-    {
-        // TODO Auto-generated method stub
-        return false;
-    }
-
-    /**
-     * @throws IOException
-     */
-    @Override
-    public void removeAll()
-        throws IOException
-    {
-        // TODO Auto-generated method stub
-
-    }
-
-    /**
-     * @throws IOException
-     */
-    @Override
-    public void dispose()
-        throws IOException
-    {
-        // TODO Auto-generated method stub
-
-    }
-
-    /**
-     * @return int
-     */
-    @Override
-    public int getSize()
-    {
-        // TODO Auto-generated method stub
-        return 0;
-    }
-
-    /**
-     * @return int
-     */
-    @Override
-    public CacheStatus getStatus()
-    {
-        return status;
-    }
-
-    /**
-     * @return null
-     */
-    @Override
-    public String getCacheName()
-    {
-        return null;
-    }
-
-    /**
-     * Return the keys in this cache.
-     * <p>
-     * @see org.apache.commons.jcs.auxiliary.disk.AbstractDiskCache#getKeySet()
-     */
-    @Override
-    public Set<K> getKeySet() throws IOException
-    {
-        return null;
-    }
-
-    /**
-     * @return null
-     */
-    @Override
-    public IStats getStatistics()
-    {
-        return null;
-    }
-
-    /**
-     * @return null
-     */
-    @Override
-    public String getStats()
-    {
-        return null;
-    }
-
-    /**
-     * @return cacheType
-     */
-    @Override
-    public CacheType getCacheType()
-    {
-        return cacheType;
-    }
-
-    /**
-     * @return Returns the AuxiliaryCacheAttributes.
-     */
-    @Override
-    public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
-    {
-        return null;
-    }
-
-    /** @return null */
-    @Override
-    public String getEventLoggingExtraInfo()
-    {
-        return null;
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/MockAuxiliaryCacheAttributes.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/MockAuxiliaryCacheAttributes.java
deleted file mode 100644
index 68deff8..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/MockAuxiliaryCacheAttributes.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package org.apache.commons.jcs.auxiliary;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/** For testing. */
-public class MockAuxiliaryCacheAttributes
-    extends AbstractAuxiliaryCacheAttributes
-{
-    /** Don't change. */
-    private static final long serialVersionUID = 1091238902450504108L;
-
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/MockAuxiliaryCacheFactory.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/MockAuxiliaryCacheFactory.java
deleted file mode 100644
index 26677e4..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/MockAuxiliaryCacheFactory.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package org.apache.commons.jcs.auxiliary;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-
-/** For testing */
-public class MockAuxiliaryCacheFactory
-    extends AbstractAuxiliaryCacheFactory
-{
-    /** the name of the aux */
-    public String name = "MockAuxiliaryCacheFactory";
-
-    /**
-     * Creates a mock aux.
-     * <p>
-     * @param attr
-     * @param cacheMgr
-     * @param cacheEventLogger
-     * @param elementSerializer
-     * @return AuxiliaryCache
-     */
-    @Override
-    public <K, V> AuxiliaryCache<K, V>
-        createCache( AuxiliaryCacheAttributes attr, ICompositeCacheManager cacheMgr,
-           ICacheEventLogger cacheEventLogger, IElementSerializer elementSerializer )
-    {
-        MockAuxiliaryCache<K, V> auxCache = new MockAuxiliaryCache<>();
-        auxCache.setCacheEventLogger( cacheEventLogger );
-        auxCache.setElementSerializer( elementSerializer );
-        return auxCache;
-    }
-
-    /**
-     * @return String
-     */
-    @Override
-    public String getName()
-    {
-        return name;
-    }
-
-    /**
-     * @param s
-     */
-    @Override
-    public void setName( String s )
-    {
-        this.name = s;
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/MockCacheEventLogger.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/MockCacheEventLogger.java
deleted file mode 100644
index e049ca0..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/MockCacheEventLogger.java
+++ /dev/null
@@ -1,98 +0,0 @@
-package org.apache.commons.jcs.auxiliary;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.logging.CacheEvent;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEvent;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * For testing auxiliary event logging. Improve later so we can test the details. This is very
- * crude.
- */
-public class MockCacheEventLogger
-    implements ICacheEventLogger
-{
-    /** times called */
-    public int applicationEventCalls = 0;
-
-    /** times called */
-    public int startICacheEventCalls = 0;
-
-    /** times called */
-    public int endICacheEventCalls = 0;
-
-    /** times called */
-    public int errorEventCalls = 0;
-
-    /** list of messages */
-    public List<String> errorMessages = new ArrayList<>();
-
-    /**
-     * @param source
-     * @param eventName
-     * @param optionalDetails
-     */
-    @Override
-    public void logApplicationEvent( String source, String eventName, String optionalDetails )
-    {
-        applicationEventCalls++;
-    }
-
-    /**
-     * @param event
-     */
-    @Override
-    public <T> void logICacheEvent( ICacheEvent<T> event )
-    {
-        endICacheEventCalls++;
-    }
-
-    /**
-     * @param source
-     * @param eventName
-     * @param errorMessage
-     */
-    @Override
-    public void logError( String source, String eventName, String errorMessage )
-    {
-        errorEventCalls++;
-        errorMessages.add( errorMessage );
-    }
-
-    /**
-     * @param source
-     * @param region
-     * @param eventName
-     * @param optionalDetails
-     * @param key
-     * @return ICacheEvent
-     */
-    @Override
-    public <T> ICacheEvent<T> createICacheEvent( String source, String region,
-            String eventName, String optionalDetails, T key )
-    {
-        startICacheEventCalls++;
-        return new CacheEvent<>();
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/AbstractDiskCacheUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/AbstractDiskCacheUnitTest.java
deleted file mode 100644
index 14f6445..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/AbstractDiskCacheUnitTest.java
+++ /dev/null
@@ -1,301 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.io.StringWriter;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-import junit.framework.TestCase;
-
-import org.apache.commons.jcs.TestLogConfigurationUtil;
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes;
-import org.apache.commons.jcs.auxiliary.disk.behavior.IDiskCacheAttributes;
-import org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes;
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.CacheStatus;
-import org.apache.commons.jcs.engine.ElementAttributes;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-
-/** Tests for the abstract disk cache. It's largely tested by actual instances. */
-public class AbstractDiskCacheUnitTest
-    extends TestCase
-{
-    /**
-     * Verify that update and get work.
-     * <p>
-     * @throws IOException
-     */
-    public void testUpdateGet_allowed()
-        throws IOException
-    {
-        // SETUP
-        String cacheName = "testUpdateGet_allowed";
-        IDiskCacheAttributes diskCacheAttributes = new IndexedDiskCacheAttributes();
-        diskCacheAttributes.setCacheName( cacheName );
-
-        AbstractDiskCacheTestInstance<String, String> diskCache = new AbstractDiskCacheTestInstance<>( diskCacheAttributes );
-
-        String key = "myKey";
-        String value = "myValue";
-        IElementAttributes elementAttributes = new ElementAttributes();
-        ICacheElement<String, String> cacheElement = new CacheElement<>( cacheName, key, value, elementAttributes );
-
-        diskCache.update( cacheElement );
-
-        // DO WORK
-        ICacheElement<String, String> result = diskCache.get( key );
-
-        // VERIFY
-        //System.out.println( diskCache.getStats() );
-        assertNotNull( "Item should be in the map.", result );
-    }
-
-    /**
-     * Verify that alive is set to false..
-     * <p>
-     * @throws IOException
-     */
-    public void testDispose()
-        throws IOException
-    {
-        // SETUP
-        String cacheName = "testDispose";
-        IDiskCacheAttributes diskCacheAttributes = new IndexedDiskCacheAttributes();
-        diskCacheAttributes.setCacheName( cacheName );
-
-        AbstractDiskCacheTestInstance<String, String> diskCache = new AbstractDiskCacheTestInstance<>( diskCacheAttributes );
-
-        String key = "myKey";
-        String value = "myValue";
-        IElementAttributes elementAttributes = new ElementAttributes();
-        ICacheElement<String, String> cacheElement = new CacheElement<>( cacheName, key, value, elementAttributes );
-
-        diskCache.update( cacheElement );
-
-        // DO WORK
-        diskCache.dispose();
-
-        // VERIFY
-        assertFalse( "disk cache should not be alive.", diskCache.isAlive() );
-        assertEquals( "Status should be disposed", CacheStatus.DISPOSED, diskCache.getStatus() );
-    }
-
-    /**
-     * Verify that removeAll is prohibited.
-     * <p>
-     * @throws IOException
-     */
-    public void testRemoveAll_notAllowed()
-        throws IOException
-    {
-        // SETUP
-        StringWriter stringWriter = new StringWriter();
-        TestLogConfigurationUtil.configureLogger( stringWriter, AbstractDiskCache.class.getName() );
-
-        IDiskCacheAttributes diskCacheAttributes = new IndexedDiskCacheAttributes();
-        diskCacheAttributes.setAllowRemoveAll( false );
-
-        AbstractDiskCacheTestInstance<String, String> diskCache = new AbstractDiskCacheTestInstance<>( diskCacheAttributes );
-
-        String cacheName = "testRemoveAll_notAllowed";
-        String key = "myKey";
-        String value = "myValue";
-        IElementAttributes elementAttributes = new ElementAttributes();
-        ICacheElement<String, String> cacheElement = new CacheElement<>( cacheName, key, value, elementAttributes );
-
-        diskCache.update( cacheElement );
-
-        // DO WORK
-        diskCache.removeAll();
-        String result = stringWriter.toString();
-
-        // VERIFY
-        assertTrue( "Should say not allowed.", result.indexOf( "set to false" ) != -1 );
-        assertNotNull( "Item should be in the map.", diskCache.get( key ) );
-    }
-
-    /**
-     * Verify that removeAll is allowed.
-     * <p>
-     * @throws IOException
-     */
-    public void testRemoveAll_allowed()
-        throws IOException
-    {
-        // SETUP
-        IDiskCacheAttributes diskCacheAttributes = new IndexedDiskCacheAttributes();
-        diskCacheAttributes.setAllowRemoveAll( true );
-
-        AbstractDiskCacheTestInstance<String, String> diskCache = new AbstractDiskCacheTestInstance<>( diskCacheAttributes );
-
-        String cacheName = "testRemoveAll_allowed";
-        String key = "myKey";
-        String value = "myValue";
-        IElementAttributes elementAttributes = new ElementAttributes();
-        ICacheElement<String, String> cacheElement = new CacheElement<>( cacheName, key, value, elementAttributes );
-
-        diskCache.update( cacheElement );
-
-        // DO WORK
-        diskCache.removeAll();
-
-        // VERIFY
-        assertNull( "Item should not be in the map.", diskCache.get( key ) );
-    }
-
-    /** Concrete, testable instance. */
-    protected static class AbstractDiskCacheTestInstance<K, V>
-        extends AbstractDiskCache<K, V>
-    {
-        /** Internal map */
-        protected Map<K, ICacheElement<K, V>> map = new HashMap<>();
-
-        /** used by the abstract aux class */
-        protected IDiskCacheAttributes diskCacheAttributes;
-
-        /**
-         * Creates the disk cache.
-         * <p>
-         * @param attr
-         */
-        public AbstractDiskCacheTestInstance( IDiskCacheAttributes attr )
-        {
-            super( attr );
-            diskCacheAttributes = attr;
-            setAlive(true);
-        }
-
-        /**
-         * The location on disk
-         * <p>
-         * @return "memory"
-         */
-        @Override
-        protected String getDiskLocation()
-        {
-            return "memory";
-        }
-
-        /**
-         * Return the keys in this cache.
-         * <p>
-         * @see org.apache.commons.jcs.auxiliary.disk.AbstractDiskCache#getKeySet()
-         */
-        @Override
-        public Set<K> getKeySet() throws IOException
-        {
-            return new HashSet<>(map.keySet());
-        }
-
-        /**
-         * @return map.size()
-         */
-        @Override
-        public int getSize()
-        {
-            return map.size();
-        }
-
-        /**
-         * @throws IOException
-         */
-        @Override
-        protected void processDispose()
-            throws IOException
-        {
-            //System.out.println( "processDispose" );
-        }
-
-        /**
-         * @param key
-         * @return ICacheElement
-         * @throws IOException
-         */
-        @Override
-        protected ICacheElement<K, V> processGet( K key )
-            throws IOException
-        {
-            //System.out.println( "processGet: " + key );
-            return map.get( key );
-        }
-
-        /**
-         * @param pattern
-         * @return Collections.EMPTY_MAP
-         * @throws IOException
-         */
-        @Override
-        protected Map<K, ICacheElement<K, V>> processGetMatching( String pattern )
-            throws IOException
-        {
-            return Collections.emptyMap();
-        }
-
-        /**
-         * @param key
-         * @return false
-         * @throws IOException
-         */
-        @Override
-        protected boolean processRemove( K key )
-            throws IOException
-        {
-            return map.remove( key ) != null;
-        }
-
-        /**
-         * @throws IOException
-         */
-        @Override
-        protected void processRemoveAll()
-            throws IOException
-        {
-            //System.out.println( "processRemoveAll" );
-            map.clear();
-        }
-
-        /**
-         * @param cacheElement
-         * @throws IOException
-         */
-        @Override
-        protected void processUpdate( ICacheElement<K, V> cacheElement )
-            throws IOException
-        {
-            //System.out.println( "processUpdate: " + cacheElement );
-            map.put( cacheElement.getKey(), cacheElement );
-        }
-
-        /**
-         * @return null
-         */
-        @Override
-        public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
-        {
-            return diskCacheAttributes;
-        }
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/DiskTestObject.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/DiskTestObject.java
deleted file mode 100644
index b9927c1..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/DiskTestObject.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.Serializable;
-import java.util.Arrays;
-
-/**
- * Resembles a cached image.
- */
-public class DiskTestObject implements Serializable
-{
-    /** don't change */
-    private static final long serialVersionUID = 1L;
-
-    /**
-     * Key
-     */
-    public Integer id;
-
-    /**
-     * Byte size
-     */
-    public byte[] imageBytes;
-
-    /**
-     * @param id
-     * @param imageBytes
-     */
-    public DiskTestObject(Integer id, byte[] imageBytes)
-    {
-        this.id = id;
-        this.imageBytes = imageBytes;
-    }
-
-    /**
-     * @see java.lang.Object#equals(Object other)
-     */
-    @Override
-    public boolean equals(Object other)
-    {
-        if (other instanceof DiskTestObject)
-        {
-            DiskTestObject o = (DiskTestObject) other;
-            if (id != null)
-                return id.equals(o.id) && Arrays.equals(imageBytes, o.imageBytes);
-            else if (id == null && o.id == null) return Arrays.equals(imageBytes, o.imageBytes);
-        }
-        return false;
-    }
-
-    /**
-     * @see java.lang.Object#hashCode()
-     */
-    @Override
-    public int hashCode()
-    {
-        return id.hashCode();
-    }
-
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/PurgatoryElementUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/PurgatoryElementUnitTest.java
deleted file mode 100644
index 5b937d2..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/PurgatoryElementUnitTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.ElementAttributes;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-
-/** Simple unit tests for the Purgatory Element. */
-public class PurgatoryElementUnitTest
-    extends TestCase
-{
-    /** Verify basic data */
-    public void testSpoolable_normal()
-    {
-        // SETUP
-        String cacheName = "myCacheName";
-        String key = "myKey";
-        String value = "myValue";
-        IElementAttributes elementAttributes = new ElementAttributes();
-        ICacheElement<String, String> cacheElement = new CacheElement<>( cacheName, key, value, elementAttributes );
-        PurgatoryElement<String, String> purgatoryElement = new PurgatoryElement<>( cacheElement );
-        purgatoryElement.setSpoolable( false );
-
-        // DO WORK
-        boolean result = purgatoryElement.isSpoolable();
-
-        // VERIFY
-        assertFalse( "Should not be spoolable.", result );
-    }
-
-    /** Verify basic data */
-    public void testElementAttributes_normal()
-    {
-        // SETUP
-        String cacheName = "myCacheName";
-        String key = "myKey";
-        String value = "myValue";
-        IElementAttributes elementAttributes = new ElementAttributes();
-
-        ICacheElement<String, String> cacheElement = new CacheElement<>( cacheName, key, value );
-        PurgatoryElement<String, String> purgatoryElement = new PurgatoryElement<>( cacheElement );
-        purgatoryElement.setElementAttributes( elementAttributes );
-
-        // DO WORK
-        IElementAttributes result = cacheElement.getElementAttributes();
-
-        // VERIFY
-        assertEquals( "Should have set the attributes on the element", elementAttributes, result );
-    }
-
-    /** Verify basic data */
-    public void testToString_normal()
-    {
-        // SETUP
-        String cacheName = "myCacheName";
-        String key = "myKey";
-        String value = "myValue";
-        IElementAttributes elementAttributes = new ElementAttributes();
-        ICacheElement<String, String> cacheElement = new CacheElement<>( cacheName, key, value, elementAttributes );
-        PurgatoryElement<String, String> purgatoryElement = new PurgatoryElement<>( cacheElement );
-
-        // DO WORK
-        String result = purgatoryElement.toString();
-
-        // VERIFY
-        assertTrue( "Should have the cacheName.", result.indexOf( cacheName ) != -1 );
-        assertTrue( "Should have the key.", result.indexOf( key ) != -1 );
-        assertTrue( "Should have the value.", result.indexOf( value ) != -1 );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCacheConcurrentUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCacheConcurrentUnitTest.java
deleted file mode 100644
index 1a4f4dc..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCacheConcurrentUnitTest.java
+++ /dev/null
@@ -1,258 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.block;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.extensions.ActiveTestSuite;
-import junit.framework.Test;
-import junit.framework.TestCase;
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Test which exercises the block disk cache. This one uses three different
- * regions for three threads.
- */
-public class BlockDiskCacheConcurrentUnitTest
-    extends TestCase
-{
-    /**
-     * Number of items to cache, twice the configured maxObjects for the memory
-     * cache regions.
-     */
-    private static int items = 200;
-
-    /**
-     * Constructor for the TestDiskCache object.
-     *
-     * @param testName
-     * @throws Exception
-     */
-    public BlockDiskCacheConcurrentUnitTest( String testName )
-        throws Exception
-    {
-        super( testName );
-    }
-
-    /**
-     * Main method passes this test to the text test runner.
-     *
-     * @param args
-     */
-    public static void main( String args[] )
-    {
-        String[] testCaseName = { BlockDiskCacheConcurrentUnitTest.class.getName() };
-        junit.textui.TestRunner.main( testCaseName );
-    }
-
-    /**
-     * A unit test suite for JUnit
-     *
-     * @return The test suite
-     * @throws Exception
-     */
-    public static Test suite()
-        throws Exception
-    {
-        ActiveTestSuite suite = new ActiveTestSuite();
-
-        JCS.setConfigFilename( "/TestBlockDiskCache.ccf" );
-        JCS.getInstance( "indexedRegion1" ).clear();
-        JCS.getInstance( "indexedRegion2" ).clear();
-        JCS.getInstance( "indexedRegion3" ).clear();
-
-        suite.addTest( new BlockDiskCacheConcurrentUnitTest( "testBlockDiskCache1" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "indexedRegion1" );
-            }
-        } );
-
-        suite.addTest( new BlockDiskCacheConcurrentUnitTest( "testBlockDiskCache2" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "indexedRegion2" );
-            }
-        } );
-
-        suite.addTest( new BlockDiskCacheConcurrentUnitTest( "testBlockDiskCache3" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "indexedRegion3" );
-            }
-        } );
-
-        suite.addTest( new BlockDiskCacheConcurrentUnitTest( "testBlockDiskCache4" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegionInRange( "indexedRegion3", 300, 600 );
-            }
-        } );
-
-        return suite;
-    }
-
-    /**
-     * Test setup
-     */
-    @Override
-    public void setUp()
-    {
-        JCS.setConfigFilename( "/TestBlockDiskCache.ccf" );
-    }
-
-    /**
-     * Adds items to cache, gets them, and removes them. The item count is more
-     * than the size of the memory cache, so items should spool to disk.
-     *
-     * @param region
-     *            Name of the region to access
-     *
-     * @throws Exception
-     *                If an error occurs
-     */
-    public void runTestForRegion( String region )
-        throws Exception
-    {
-        CacheAccess<String, String> jcs = JCS.getInstance( region );
-
-        // Add items to cache
-        for ( int i = 0; i <= items; i++ )
-        {
-            jcs.put( i + ":key", region + " data " + i );
-        }
-
-        // Test that all items are in cache
-        for ( int i = 0; i <= items; i++ )
-        {
-            String value = jcs.get( i + ":key" );
-
-            assertEquals( region + " data " + i, value );
-        }
-
-        // Test that getElements returns all the expected values
-        Set<String> keys = new HashSet<>();
-        for ( int i = 0; i <= items; i++ )
-        {
-            keys.add( i + ":key" );
-        }
-
-        Map<String, ICacheElement<String, String>> elements = jcs.getCacheElements( keys );
-        for ( int i = 0; i <= items; i++ )
-        {
-            ICacheElement<String, String> element = elements.get( i + ":key" );
-            assertNotNull( "element " + i + ":key is missing", element );
-            assertEquals( "value " + i + ":key", region + " data " + i, element.getVal() );
-        }
-
-        // Remove all the items
-        for ( int i = 0; i <= items; i++ )
-        {
-            jcs.remove( i + ":key" );
-        }
-
-        // Verify removal
-        // another thread may have inserted since
-        for ( int i = 0; i <= items; i++ )
-        {
-            assertNull( "Removed key should be null: " + i + ":key" + "\n stats " + jcs.getStats(), jcs
-                .get( i + ":key" ) );
-        }
-    }
-
-    /**
-     * Adds items to cache, gets them, and removes them. The item count is more
-     * than the size of the memory cache, so items should spool to disk.
-     *
-     * @param region
-     *            Name of the region to access
-     * @param start
-     * @param end
-     *
-     * @throws Exception
-     *                If an error occurs
-     */
-    public void runTestForRegionInRange( String region, int start, int end )
-        throws Exception
-    {
-        CacheAccess<String, String> jcs = JCS.getInstance( region );
-
-        // Add items to cache
-        for ( int i = start; i <= end; i++ )
-        {
-            jcs.put( i + ":key", region + " data " + i );
-        }
-
-        // Test that all items are in cache
-        for ( int i = start; i <= end; i++ )
-        {
-            String value = jcs.get( i + ":key" );
-
-            assertEquals( region + " data " + i, value );
-        }
-
-        // Test that getElements returns all the expected values
-        Set<String> keys = new HashSet<>();
-        for ( int i = start; i <= end; i++ )
-        {
-            keys.add( i + ":key" );
-        }
-
-        Map<String, ICacheElement<String, String>> elements = jcs.getCacheElements( keys );
-        for ( int i = start; i <= end; i++ )
-        {
-            ICacheElement<String, String> element = elements.get( i + ":key" );
-            assertNotNull( "element " + i + ":key is missing", element );
-            assertEquals( "value " + i + ":key", region + " data " + i, element.getVal() );
-        }
-
-        // Remove all the items
-        for ( int i = start; i <= end; i++ )
-        {
-            jcs.remove( i + ":key" );
-        }
-
-//        System.out.println( jcs.getStats() );
-
-        // Verify removal
-        // another thread may have inserted since
-        for ( int i = start; i <= end; i++ )
-        {
-            assertNull( "Removed key should be null: " + i + ":key " + "\n stats " + jcs.getStats(), jcs.get( i
-                + ":key" ) );
-        }
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCacheCountUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCacheCountUnitTest.java
deleted file mode 100644
index 5f7d57e..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCacheCountUnitTest.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.block;

-

-/*

- * Licensed to the Apache Software Foundation (ASF) under one

- * or more contributor license agreements.  See the NOTICE file

- * distributed with this work for additional information

- * regarding copyright ownership.  The ASF licenses this file

- * to you under the Apache License, Version 2.0 (the

- * "License"); you may not use this file except in compliance

- * with the License.  You may obtain a copy of the License at

- *

- *   http://www.apache.org/licenses/LICENSE-2.0

- *

- * Unless required by applicable law or agreed to in writing,

- * software distributed under the License is distributed on an

- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

- * KIND, either express or implied.  See the License for the

- * specific language governing permissions and limitations

- * under the License.

- */

-

-import org.apache.commons.jcs.auxiliary.disk.behavior.IDiskCacheAttributes.DiskLimitType;

-

-public class BlockDiskCacheCountUnitTest extends BlockDiskCacheUnitTestAbstract

-{

-

-    @Override

-    public BlockDiskCacheAttributes getCacheAttributes()

-    {

-        BlockDiskCacheAttributes ret = new BlockDiskCacheAttributes();

-        ret.setDiskLimitType(DiskLimitType.COUNT);

-        return ret;

-    }

-

-}

diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCacheKeyStoreUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCacheKeyStoreUnitTest.java
deleted file mode 100644
index 8e3da94..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCacheKeyStoreUnitTest.java
+++ /dev/null
@@ -1,190 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.block;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-
-import org.apache.commons.jcs.auxiliary.disk.behavior.IDiskCacheAttributes.DiskLimitType;
-
-/**
- * Tests for the keyStore.
- * <p>
- *
- * @author Aaron Smuts
- */
-public class BlockDiskCacheKeyStoreUnitTest
-        extends TestCase
-{
-    /** Directory name */
-    private final String rootDirName = "target/test-sandbox/block";
-
-    /**
-     * Put a bunch of keys in the key store and verify that they are present.
-     * <p>
-     *
-     * @throws Exception
-     */
-    public void testPutKeys()
-            throws Exception
-    {
-        // SETUP
-        BlockDiskCacheAttributes attributes = new BlockDiskCacheAttributes();
-        attributes.setCacheName("testPutKeys");
-        attributes.setDiskPath(rootDirName);
-        attributes.setMaxKeySize(1000);
-        attributes.setBlockSizeBytes(2000);
-
-        innerTestPutKeys(attributes);
-    }
-
-    public void testPutKeysSize()
-            throws Exception
-    {
-        // SETUP
-        BlockDiskCacheAttributes attributes = new BlockDiskCacheAttributes();
-        attributes.setCacheName("testPutKeys");
-        attributes.setDiskPath(rootDirName);
-        attributes.setMaxKeySize(100000);
-        attributes.setBlockSizeBytes(1024);
-        attributes.setDiskLimitType(DiskLimitType.SIZE);
-
-        innerTestPutKeys(attributes);
-    }
-
-    private void innerTestPutKeys(BlockDiskCacheAttributes attributes)
-    {
-        BlockDiskCache<String, String> blockDiskCache = new BlockDiskCache<>(attributes);
-        BlockDiskKeyStore<String> keyStore = new BlockDiskKeyStore<>(attributes, blockDiskCache);
-
-        // DO WORK
-        int numElements = 100;
-        for (int i = 0; i < numElements; i++)
-        {
-            keyStore.put(String.valueOf(i), new int[i]);
-        }
-        // System.out.println( "testPutKeys " + keyStore );
-
-        // VERIFY
-        assertEquals("Wrong number of keys", numElements, keyStore.size());
-        for (int i = 0; i < numElements; i++)
-        {
-            int[] result = keyStore.get(String.valueOf(i));
-            assertEquals("Wrong array returned.", i, result.length);
-        }
-    }
-
-    /**
-     * Verify that we can load keys that we saved. Add a bunch. Save them. Clear
-     * the memory key hash. Load the keys. Verify.
-     * <p>
-     *
-     * @throws Exception
-     */
-    public void testSaveLoadKeys()
-            throws Exception
-    {
-        // SETUP
-        BlockDiskCacheAttributes attributes = new BlockDiskCacheAttributes();
-        attributes.setCacheName("testSaveLoadKeys");
-        attributes.setDiskPath(rootDirName);
-        attributes.setMaxKeySize(10000);
-        attributes.setBlockSizeBytes(2000);
-
-        testSaveLoadKeysInner(attributes);
-    }
-
-    public void testSaveLoadKeysSize()
-            throws Exception
-    {
-        // SETUP
-        BlockDiskCacheAttributes attributes = new BlockDiskCacheAttributes();
-        attributes.setCacheName("testSaveLoadKeys");
-        attributes.setDiskPath(rootDirName);
-        attributes.setMaxKeySize(10000);
-        attributes.setBlockSizeBytes(2000);
-
-        testSaveLoadKeysInner(attributes);
-    }
-
-    private void testSaveLoadKeysInner(BlockDiskCacheAttributes attributes)
-    {
-        BlockDiskKeyStore<String> keyStore = new BlockDiskKeyStore<>(attributes, null);
-
-        // DO WORK
-        int numElements = 1000;
-        int blockIndex = 0;
-        // Random random = new Random( 89 );
-        for (int i = 0; i < numElements; i++)
-        {
-            int blocks = i;// random.nextInt( 10 );
-
-            // fill with reasonable data to make verify() happy
-            int[] block1 = new int[blocks];
-            int[] block2 = new int[blocks];
-            for (int j = 0; j < blocks; j++)
-            {
-                block1[j] = blockIndex++;
-                block2[j] = blockIndex++;
-            }
-            keyStore.put(String.valueOf(i), block1);
-            keyStore.put(String.valueOf(i), block2);
-        }
-        // System.out.println( "testSaveLoadKeys " + keyStore );
-
-        // VERIFY
-        assertEquals("Wrong number of keys", numElements, keyStore.size());
-
-        // DO WORK
-        keyStore.saveKeys();
-        keyStore.clearMemoryMap();
-
-        // VERIFY
-        assertEquals("Wrong number of keys after clearing memory", 0, keyStore.size());
-
-        // DO WORK
-        keyStore.loadKeys();
-
-        // VERIFY
-        assertEquals("Wrong number of keys after loading", numElements, keyStore.size());
-        for (int i = 0; i < numElements; i++)
-        {
-            int[] result = keyStore.get(String.valueOf(i));
-            assertEquals("Wrong array returned.", i, result.length);
-        }
-    }
-
-    public void testObjectLargerThanMaxSize()
-    {
-        BlockDiskCacheAttributes attributes = new BlockDiskCacheAttributes();
-        attributes.setCacheName("testObjectLargerThanMaxSize");
-        attributes.setDiskPath(rootDirName);
-        attributes.setMaxKeySize(1000);
-        attributes.setBlockSizeBytes(2000);
-        attributes.setDiskLimitType(DiskLimitType.SIZE);
-
-        @SuppressWarnings({ "unchecked", "rawtypes" })
-        BlockDiskKeyStore<String> keyStore = new BlockDiskKeyStore<>(attributes, new BlockDiskCache(attributes));
-
-        keyStore.put("1", new int[1000]);
-        keyStore.put("2", new int[1000]);
-        assertNull(keyStore.get("1"));
-        assertNotNull(keyStore.get("2"));
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCacheRandomConcurrentTestUtil.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCacheRandomConcurrentTestUtil.java
deleted file mode 100644
index c76f582..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCacheRandomConcurrentTestUtil.java
+++ /dev/null
@@ -1,91 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.block;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.access.TestCacheAccess;
-import org.junit.Test;
-
-/**
- * This is used by other tests to generate a random load on the disk cache.
- */
-public class BlockDiskCacheRandomConcurrentTestUtil
-    extends TestCase
-{
-    /**
-     * Constructor for the TestDiskCache object.
-     *
-     * @param testName
-     */
-    public BlockDiskCacheRandomConcurrentTestUtil( String testName )
-    {
-        super( testName );
-    }
-    
-    @Test
-    public void test()
-    {
-       
-    }
-
-    /**
-     * Randomly adds items to cache, gets them, and removes them. The range
-     * count is more than the size of the memory cache, so items should spool to
-     * disk.
-     * <p>
-     * @param region
-     *            Name of the region to access
-     * @param range
-     * @param numOps
-     * @param testNum
-     *
-     * @throws Exception
-     *                If an error occurs
-     */
-    public void runTestForRegion( String region, int range, int numOps, int testNum )
-        throws Exception
-    {
-        // run a rondom operation test to detect deadlocks
-        TestCacheAccess tca = new TestCacheAccess( "/TestBlockDiskCacheCon.ccf" );
-        tca.setRegion( region );
-        tca.random( range, numOps );
-
-        // make sure a simple put then get works
-        // this may fail if the other tests are flooding the disk cache
-        CacheAccess<String, String> jcs = JCS.getInstance( region );
-        String key = "testKey" + testNum;
-        String data = "testData" + testNum;
-        jcs.put( key, data );
-        String value = jcs.get( key );
-        assertEquals( data, value );
-    }
-
-    /**
-     * Test setup
-     */
-    @Override
-    public void setUp()
-    {
-        JCS.setConfigFilename( "/TestBlockDiskCacheCon.ccf" );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCacheSameRegionConcurrentUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCacheSameRegionConcurrentUnitTest.java
deleted file mode 100644
index 1cb9656..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCacheSameRegionConcurrentUnitTest.java
+++ /dev/null
@@ -1,172 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.block;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.extensions.ActiveTestSuite;
-import junit.framework.Test;
-import junit.framework.TestCase;
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Test which exercises the block disk cache. Runs three threads against the same region.
- */
-public class BlockDiskCacheSameRegionConcurrentUnitTest
-    extends TestCase
-{
-    /**
-     * Constructor for the TestDiskCache object.
-     * <p>
-     * @param testName
-     */
-    public BlockDiskCacheSameRegionConcurrentUnitTest( String testName )
-    {
-        super( testName );
-    }
-
-    /**
-     * Main method passes this test to the text test runner.
-     * <p>
-     * @param args
-     * @throws InterruptedException
-     */
-    public static void main( String args[] ) throws InterruptedException
-    {
-        String[] testCaseName = { BlockDiskCacheSameRegionConcurrentUnitTest.class.getName() };
-        junit.textui.TestRunner.main( testCaseName );
-
-        // Give test threads some time to finish
-        Thread.sleep(2000);
-    }
-
-    /**
-     * A unit test suite for JUnit
-     * @return The test suite
-     */
-    public static Test suite()
-    {
-        ActiveTestSuite suite = new ActiveTestSuite();
-
-        suite.addTest( new BlockDiskCacheSameRegionConcurrentUnitTest( "testBlockDiskCache1" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "blockRegion4", 0, 200 );
-            }
-        } );
-
-        suite.addTest( new BlockDiskCacheSameRegionConcurrentUnitTest( "testBlockDiskCache2" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "blockRegion4", 1000, 1200 );
-            }
-        } );
-
-        suite.addTest( new BlockDiskCacheSameRegionConcurrentUnitTest( "testBlockDiskCache3" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "blockRegion4", 2000, 2200 );
-            }
-        } );
-
-        suite.addTest( new BlockDiskCacheSameRegionConcurrentUnitTest( "testBlockDiskCache4" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "blockRegion4", 2200, 5200 );
-            }
-        } );
-
-        return suite;
-    }
-
-    /**
-     * Test setup.  Sets the config name and clears the region.
-     * <p>
-     * @throws Exception
-     */
-    @Override
-    public void setUp()
-        throws Exception
-    {
-        JCS.setConfigFilename( "/TestBlockDiskCacheCon.ccf" );
-    }
-
-    /**
-     * Adds items to cache, gets them, and removes them. The item count is more than the size of the
-     * memory cache, so items should spool to disk.
-     * @param region Name of the region to access
-     * @param start
-     * @param end
-     * @throws Exception If an error occurs
-     */
-    public void runTestForRegion( String region, int start, int end )
-        throws Exception
-    {
-        CacheAccess<String, String> jcs = JCS.getInstance( region );
-
-        // Add items to cache
-
-        for ( int i = start; i <= end; i++ )
-        {
-            jcs.put( i + ":key", region + " data " + i + "-" + region );
-        }
-
-        // Test that all items are in cache
-
-        for ( int i = start; i <= end; i++ )
-        {
-            String key = i + ":key";
-            String value = jcs.get( key );
-
-            assertEquals( "Wrong value for key [" + key + "]", region + " data " + i + "-" + region, value );
-        }
-
-        // Test that getElements returns all the expected values
-        Set<String> keys = new HashSet<>();
-        for ( int i = start; i <= end; i++ )
-        {
-            keys.add( i + ":key" );
-        }
-
-        Map<String, ICacheElement<String, String>> elements = jcs.getCacheElements( keys );
-        for ( int i = start; i <= end; i++ )
-        {
-            ICacheElement<String, String> element = elements.get( i + ":key" );
-            assertNotNull( "element " + i + ":key is missing", element );
-            assertEquals( "value " + i + ":key", region + " data " + i + "-" + region, element.getVal() );
-        }
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCacheSizeUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCacheSizeUnitTest.java
deleted file mode 100644
index 4a093ff..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCacheSizeUnitTest.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.block;

-

-/*

- * Licensed to the Apache Software Foundation (ASF) under one

- * or more contributor license agreements.  See the NOTICE file

- * distributed with this work for additional information

- * regarding copyright ownership.  The ASF licenses this file

- * to you under the Apache License, Version 2.0 (the

- * "License"); you may not use this file except in compliance

- * with the License.  You may obtain a copy of the License at

- *

- *   http://www.apache.org/licenses/LICENSE-2.0

- *

- * Unless required by applicable law or agreed to in writing,

- * software distributed under the License is distributed on an

- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

- * KIND, either express or implied.  See the License for the

- * specific language governing permissions and limitations

- * under the License.

- */

-

-import org.apache.commons.jcs.auxiliary.disk.behavior.IDiskCacheAttributes.DiskLimitType;

-

-public class BlockDiskCacheSizeUnitTest extends BlockDiskCacheUnitTestAbstract

-{

-

-    @Override

-    public BlockDiskCacheAttributes getCacheAttributes()

-    {

-        BlockDiskCacheAttributes ret = new BlockDiskCacheAttributes();

-        ret.setDiskLimitType(DiskLimitType.SIZE);

-        return ret;

-    }

-

-}

diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCacheSteadyLoadTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCacheSteadyLoadTest.java
deleted file mode 100644
index 135131e..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCacheSteadyLoadTest.java
+++ /dev/null
@@ -1,161 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.block;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.text.DecimalFormat;
-import java.util.Random;
-
-import junit.framework.TestCase;
-
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.auxiliary.disk.DiskTestObject;
-
-/**
- * This allows you to put thousands of large objects into the disk cache and to force removes to
- * trigger optimizations along the way.
- * <p>
- * @author Aaron Smuts
- */
-public class BlockDiskCacheSteadyLoadTest
-    extends TestCase
-{
-    /** String for separating log entries. */
-    private static final String LOG_DIVIDER = "---------------------------";
-
-    /** the runtime. */
-    private static Runtime rt = Runtime.getRuntime();
-
-    /** The decimal format to use int he logs. */
-    private static DecimalFormat format = new DecimalFormat( "#,###" );
-
-    /**
-     * Insert 2000 wait 1 second, repeat. Average 1000 / sec.
-     * <p>
-     * @throws Exception
-     */
-    public void testRunSteadyLoadTest()
-        throws Exception
-    {
-        JCS.setConfigFilename( "/TestBlockDiskCacheSteadyLoad.ccf" );
-
-        logMemoryUsage();
-
-        int numPerRun = 250;
-        long pauseBetweenRuns = 1000;
-        int runCount = 0;
-        int runs = 1000;
-        int upperKB = 50;
-
-        CacheAccess<String, DiskTestObject> jcs = JCS.getInstance( ( numPerRun / 2 ) + "aSecond" );
-
-//        ElapsedTimer timer = new ElapsedTimer();
-        int numToGet = numPerRun * ( runs / 10 );
-        for ( int i = 0; i < numToGet; i++ )
-        {
-            jcs.get( String.valueOf( i ) );
-        }
-//        System.out.println( LOG_DIVIDER );
-//        System.out.println( "After getting " + numToGet );
-//        System.out.println( "Elapsed " + timer.getElapsedTimeString() );
-        logMemoryUsage();
-
-        jcs.clear();
-        Thread.sleep( 3000 );
-//        System.out.println( LOG_DIVIDER );
-//        System.out.println( "Start putting" );
-
-//        long totalSize = 0;
-        int totalPut = 0;
-
-        Random random = new Random( 89 );
-        while ( runCount < runs )
-        {
-            runCount++;
-            for ( int i = 0; i < numPerRun; i++ )
-            {
-                // 1/2 upper to upperKB-4 KB
-                int kiloBytes = Math.max( upperKB / 2, random.nextInt( upperKB ) );
-                int bytes = ( kiloBytes ) * 1024;
-//                totalSize += bytes;
-                totalPut++;
-                DiskTestObject object = new DiskTestObject( Integer.valueOf( i ), new byte[bytes] );
-                jcs.put( String.valueOf( totalPut ), object );
-            }
-
-            // get half of those inserted the previous run
-            if ( runCount > 1 )
-            {
-                for ( int j = ( ( totalPut - numPerRun ) - ( numPerRun / 2 ) ); j < ( totalPut - numPerRun ); j++ )
-                {
-                    jcs.get( String.valueOf( j ) );
-                }
-            }
-
-            // remove half of those inserted the previous run
-            if ( runCount > 1 )
-            {
-                for ( int j = ( ( totalPut - numPerRun ) - ( numPerRun / 2 ) ); j < ( totalPut - numPerRun ); j++ )
-                {
-                    jcs.remove( String.valueOf( j ) );
-                }
-            }
-
-
-            Thread.sleep( pauseBetweenRuns );
-            if ( runCount % 100 == 0 )
-            {
-//                System.out.println( LOG_DIVIDER );
-//                System.out.println( "Elapsed " + timer.getElapsedTimeString() );
-//                System.out.println( "Run count: " + runCount + " Average size: " + ( totalSize / totalPut ) + "\n"
-//                    + jcs.getStats() );
-                logMemoryUsage();
-            }
-        }
-
-        Thread.sleep( 3000 );
-//        System.out.println( jcs.getStats() );
-        logMemoryUsage();
-
-        Thread.sleep( 10000 );
-//        System.out.println( jcs.getStats() );
-        logMemoryUsage();
-
-        System.gc();
-        Thread.sleep( 3000 );
-        System.gc();
-//        System.out.println( jcs.getStats() );
-        logMemoryUsage();
-    }
-
-    /**
-     * Logs the memory usage.
-     */
-    private static void logMemoryUsage()
-    {
-        long byte2MB = 1024 * 1024;
-        long total = rt.totalMemory() / byte2MB;
-        long free = rt.freeMemory() / byte2MB;
-        long used = total - free;
-        System.out.println( LOG_DIVIDER );
-        System.out.println( "Memory:" + " Used:" + format.format( used ) + "MB" + " Free:" + format.format( free )
-            + "MB" + " Total:" + format.format( total ) + "MB" );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCacheUnitTestAbstract.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCacheUnitTestAbstract.java
deleted file mode 100644
index fb6e7db..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskCacheUnitTestAbstract.java
+++ /dev/null
@@ -1,571 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.block;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.File;
-import java.io.IOException;
-import java.io.Serializable;
-import java.nio.charset.StandardCharsets;
-import java.util.Map;
-
-import junit.framework.TestCase;
-
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.ElementAttributes;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-import org.apache.commons.jcs.engine.control.group.GroupAttrName;
-import org.apache.commons.jcs.engine.control.group.GroupId;
-import org.apache.commons.jcs.utils.serialization.StandardSerializer;
-
-/** Unit tests for the Block Disk Cache */
-public abstract class BlockDiskCacheUnitTestAbstract extends TestCase
-{
-    public abstract BlockDiskCacheAttributes getCacheAttributes();
-
-    public void testPutGetMatching_SmallWait() throws Exception
-    {
-        // SETUP
-        int items = 200;
-
-        String cacheName = "testPutGetMatching_SmallWait";
-        BlockDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName(cacheName);
-        cattr.setMaxKeySize(100);
-        cattr.setDiskPath("target/test-sandbox/BlockDiskCacheUnitTest");
-        BlockDiskCache<String, String> diskCache = new BlockDiskCache<>(cattr);
-
-        // DO WORK
-        for (int i = 0; i <= items; i++)
-        {
-            diskCache.update(new CacheElement<>(cacheName, i + ":key", cacheName + " data " + i));
-        }
-        Thread.sleep(500);
-
-        Map<String, ICacheElement<String, String>> matchingResults = diskCache.getMatching("1.8.+");
-
-        // VERIFY
-        assertEquals("Wrong number returned", 10, matchingResults.size());
-        // System.out.println( "matchingResults.keySet() " + matchingResults.keySet() );
-        // System.out.println( "\nAFTER TEST \n" + diskCache.getStats() );
-    }
-
-    /**
-     * Test the basic get matching. With no wait this will all come from purgatory.
-     * <p>
-     *
-     * @throws Exception
-     */
-    public void testPutGetMatching_NoWait() throws Exception
-    {
-        // SETUP
-        int items = 200;
-
-        String cacheName = "testPutGetMatching_NoWait";
-        BlockDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName(cacheName);
-        cattr.setMaxKeySize(100);
-        cattr.setDiskPath("target/test-sandbox/BlockDiskCacheUnitTest");
-        BlockDiskCache<String, String> diskCache = new BlockDiskCache<>(cattr);
-
-        // DO WORK
-        for (int i = 0; i <= items; i++)
-        {
-            diskCache.update(new CacheElement<>(cacheName, i + ":key", cacheName + " data " + i));
-        }
-
-        Map<String, ICacheElement<String, String>> matchingResults = diskCache.getMatching("1.8.+");
-
-        // VERIFY
-        assertEquals("Wrong number returned", 10, matchingResults.size());
-        // System.out.println( "matchingResults.keySet() " + matchingResults.keySet() );
-        // System.out.println( "\nAFTER TEST \n" + diskCache.getStats() );
-    }
-
-    /**
-     * Verify that the block disk cache can handle a big string.
-     * <p>
-     *
-     * @throws Exception
-     */
-    public void testChunk_BigString() throws Exception
-    {
-        String string = "This is my big string ABCDEFGH";
-        StringBuilder sb = new StringBuilder();
-        sb.append(string);
-        for (int i = 0; i < 4; i++)
-        {
-            sb.append("|" + i + ":" + sb.toString()); // big string
-        }
-        string = sb.toString();
-
-        StandardSerializer elementSerializer = new StandardSerializer();
-        byte[] data = elementSerializer.serialize(string);
-
-        File file = new File("target/test-sandbox/BlockDiskCacheUnitTest/testChunk_BigString.data");
-
-        BlockDisk blockDisk = new BlockDisk(file, 200, elementSerializer);
-
-        int numBlocksNeeded = blockDisk.calculateTheNumberOfBlocksNeeded(data);
-        // System.out.println( numBlocksNeeded );
-
-        // get the individual sub arrays.
-        byte[][] chunks = blockDisk.getBlockChunks(data, numBlocksNeeded);
-
-        byte[] resultData = new byte[0];
-
-        for (short i = 0; i < chunks.length; i++)
-        {
-            byte[] chunk = chunks[i];
-            byte[] newTotal = new byte[data.length + chunk.length];
-            // copy data into the new array
-            System.arraycopy(data, 0, newTotal, 0, data.length);
-            // copy the chunk into the new array
-            System.arraycopy(chunk, 0, newTotal, data.length, chunk.length);
-            // swap the new and old.
-            resultData = newTotal;
-        }
-
-        Serializable result = elementSerializer.deSerialize(resultData, null);
-        // System.out.println( result );
-        assertEquals("wrong string after retrieval", string, result);
-        blockDisk.close();
-    }
-
-    /**
-     * Verify that the block disk cache can handle a big string.
-     * <p>
-     *
-     * @throws Exception
-     */
-    public void testPutGet_BigString() throws Exception
-    {
-        String string = "This is my big string ABCDEFGH";
-        StringBuilder sb = new StringBuilder();
-        sb.append(string);
-        for (int i = 0; i < 4; i++)
-        {
-            sb.append(" " + i + sb.toString()); // big string
-        }
-        string = sb.toString();
-
-        String cacheName = "testPutGet_BigString";
-
-        BlockDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName(cacheName);
-        cattr.setMaxKeySize(100);
-        cattr.setBlockSizeBytes(200);
-        cattr.setDiskPath("target/test-sandbox/BlockDiskCacheUnitTest");
-        BlockDiskCache<String, String> diskCache = new BlockDiskCache<>(cattr);
-
-        // DO WORK
-        diskCache.update(new CacheElement<>(cacheName, "x", string));
-
-        // VERIFY
-        assertNotNull(diskCache.get("x"));
-        Thread.sleep(1000);
-        ICacheElement<String, String> afterElement = diskCache.get("x");
-        assertNotNull(afterElement);
-        // System.out.println( "afterElement = " + afterElement );
-        String after = afterElement.getVal();
-
-        assertNotNull(after);
-        assertEquals("wrong string after retrieval", string, after);
-    }
-
-    /**
-     * Verify that the block disk cache can handle utf encoded strings.
-     * <p>
-     *
-     * @throws Exception
-     */
-    public void testUTF8String() throws Exception
-    {
-        String string = "IÒtÎrn‚tiÙn‡lizÊti¯n";
-        StringBuilder sb = new StringBuilder();
-        sb.append(string);
-        for (int i = 0; i < 4; i++)
-        {
-            sb.append(sb.toString()); // big string
-        }
-        string = sb.toString();
-
-        // System.out.println( "The string contains " + string.length() + " characters" );
-
-        String cacheName = "testUTF8String";
-
-        BlockDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName(cacheName);
-        cattr.setMaxKeySize(100);
-        cattr.setBlockSizeBytes(200);
-        cattr.setDiskPath("target/test-sandbox/BlockDiskCacheUnitTest");
-        BlockDiskCache<String, String> diskCache = new BlockDiskCache<>(cattr);
-
-        // DO WORK
-        diskCache.update(new CacheElement<>(cacheName, "x", string));
-
-        // VERIFY
-        assertNotNull(diskCache.get("x"));
-        Thread.sleep(1000);
-        ICacheElement<String, String> afterElement = diskCache.get("x");
-        assertNotNull(afterElement);
-        // System.out.println( "afterElement = " + afterElement );
-        String after = afterElement.getVal();
-
-        assertNotNull(after);
-        assertEquals("wrong string after retrieval", string, after);
-    }
-
-    /**
-     * Verify that the block disk cache can handle utf encoded strings.
-     * <p>
-     *
-     * @throws Exception
-     */
-    public void testUTF8ByteArray() throws Exception
-    {
-        String string = "IÒtÎrn‚tiÙn‡lizÊti¯n";
-        StringBuilder sb = new StringBuilder();
-        sb.append(string);
-        for (int i = 0; i < 4; i++)
-        {
-            sb.append(sb.toString()); // big string
-        }
-        string = sb.toString();
-        // System.out.println( "The string contains " + string.length() + " characters" );
-        byte[] bytes = string.getBytes(StandardCharsets.UTF_8);
-
-        String cacheName = "testUTF8ByteArray";
-
-        BlockDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName(cacheName);
-        cattr.setMaxKeySize(100);
-        cattr.setBlockSizeBytes(200);
-        cattr.setDiskPath("target/test-sandbox/BlockDiskCacheUnitTest");
-        BlockDiskCache<String, byte[]> diskCache = new BlockDiskCache<>(cattr);
-
-        // DO WORK
-        diskCache.update(new CacheElement<>(cacheName, "x", bytes));
-
-        // VERIFY
-        assertNotNull(diskCache.get("x"));
-        Thread.sleep(1000);
-        ICacheElement<String, byte[]> afterElement = diskCache.get("x");
-        assertNotNull(afterElement);
-        // System.out.println( "afterElement = " + afterElement );
-        byte[] after = afterElement.getVal();
-
-        assertNotNull(after);
-        assertEquals("wrong bytes after retrieval", bytes.length, after.length);
-        // assertEquals( "wrong bytes after retrieval", bytes, after );
-        // assertEquals( "wrong bytes after retrieval", string, new String( after, StandardCharsets.UTF_8 ) );
-
-    }
-
-    /**
-     * Verify that the block disk cache can handle utf encoded strings.
-     * <p>
-     *
-     * @throws Exception
-     */
-    public void testUTF8StringAndBytes() throws Exception
-    {
-        X before = new X();
-        String string = "IÒtÎrn‚tiÙn‡lizÊti¯n";
-        StringBuilder sb = new StringBuilder();
-        sb.append(string);
-        for (int i = 0; i < 4; i++)
-        {
-            sb.append(sb.toString()); // big string
-        }
-        string = sb.toString();
-        // System.out.println( "The string contains " + string.length() + " characters" );
-        before.string = string;
-        before.bytes = string.getBytes(StandardCharsets.UTF_8);
-
-        String cacheName = "testUTF8StringAndBytes";
-
-        BlockDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName(cacheName);
-        cattr.setMaxKeySize(100);
-        cattr.setBlockSizeBytes(500);
-        cattr.setDiskPath("target/test-sandbox/BlockDiskCacheUnitTest");
-        BlockDiskCache<String, X> diskCache = new BlockDiskCache<>(cattr);
-
-        // DO WORK
-        diskCache.update(new CacheElement<>(cacheName, "x", before));
-
-        // VERIFY
-        assertNotNull(diskCache.get("x"));
-        Thread.sleep(1000);
-        ICacheElement<String, X> afterElement = diskCache.get("x");
-        // System.out.println( "afterElement = " + afterElement );
-        X after = (afterElement.getVal());
-
-        assertNotNull(after);
-        assertEquals("wrong string after retrieval", string, after.string);
-        assertEquals("wrong bytes after retrieval", string, new String(after.bytes, StandardCharsets.UTF_8));
-
-    }
-
-    public void testLoadFromDisk() throws Exception
-    {
-        for (int i = 0; i < 20; i++)
-        { // usually after 2 time it fails
-            oneLoadFromDisk();
-        }
-    }
-
-    public void testAppendToDisk() throws Exception
-    {
-        String cacheName = "testAppendToDisk";
-        BlockDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName(cacheName);
-        cattr.setMaxKeySize(100);
-        cattr.setBlockSizeBytes(500);
-        cattr.setDiskPath("target/test-sandbox/BlockDiskCacheUnitTest");
-        BlockDiskCache<String, X> diskCache = new BlockDiskCache<>(cattr);
-        diskCache.removeAll();
-        X value1 = new X();
-        value1.string = "1234567890";
-        X value2 = new X();
-        value2.string = "0987654321";
-        diskCache.update(new CacheElement<>(cacheName, "1", value1));
-        diskCache.dispose();
-        diskCache = new BlockDiskCache<>(cattr);
-        diskCache.update(new CacheElement<>(cacheName, "2", value2));
-        diskCache.dispose();
-        diskCache = new BlockDiskCache<>(cattr);
-        assertTrue(diskCache.verifyDisk());
-        assertEquals(2, diskCache.getKeySet().size());
-        assertEquals(value1.string, diskCache.get("1").getVal().string);
-        assertEquals(value2.string, diskCache.get("2").getVal().string);
-    }
-
-    public void oneLoadFromDisk() throws Exception
-    {
-        // initialize object to be stored
-        X before = new X();
-        String string = "IÒtÎrn‚tiÙn‡lizÊti¯n";
-        StringBuilder sb = new StringBuilder();
-        sb.append(string);
-        for (int i = 0; i < 4; i++)
-        {
-            sb.append(sb.toString()); // big string
-        }
-        string = sb.toString();
-        before.string = string;
-        before.bytes = string.getBytes(StandardCharsets.UTF_8);
-
-        // initialize cache
-        String cacheName = "testLoadFromDisk";
-        BlockDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName(cacheName);
-        cattr.setMaxKeySize(100);
-        cattr.setBlockSizeBytes(500);
-        cattr.setDiskPath("target/test-sandbox/BlockDiskCacheUnitTest");
-        BlockDiskCache<String, X> diskCache = new BlockDiskCache<>(cattr);
-
-        // DO WORK
-        for (int i = 0; i < 50; i++)
-        {
-            diskCache.update(new CacheElement<>(cacheName, "x" + i, before));
-        }
-        diskCache.dispose();
-
-        // VERIFY
-        diskCache = new BlockDiskCache<>(cattr);
-
-        for (int i = 0; i < 50; i++)
-        {
-            ICacheElement<String, X> afterElement = diskCache.get("x" + i);
-            assertNotNull("Missing element from cache. Cache size: " + diskCache.getSize() + " element: x" + i, afterElement);
-            X after = (afterElement.getVal());
-
-            assertNotNull(after);
-            assertEquals("wrong string after retrieval", string, after.string);
-            assertEquals("wrong bytes after retrieval", string, new String(after.bytes, StandardCharsets.UTF_8));
-        }
-
-        diskCache.dispose();
-    }
-
-    /**
-     * Add some items to the disk cache and then remove them one by one.
-     *
-     * @throws IOException
-     */
-    public void testRemoveItems() throws IOException
-    {
-        BlockDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName("testRemoveItems");
-        cattr.setMaxKeySize(100);
-        cattr.setDiskPath("target/test-sandbox/BlockDiskCacheUnitTest");
-        BlockDiskCache<String, String> disk = new BlockDiskCache<>(cattr);
-
-        disk.processRemoveAll();
-
-        int cnt = 25;
-        for (int i = 0; i < cnt; i++)
-        {
-            IElementAttributes eAttr = new ElementAttributes();
-            eAttr.setIsSpool(true);
-            ICacheElement<String, String> element = new CacheElement<>("testRemoveItems", "key:" + i, "data:" + i);
-            element.setElementAttributes(eAttr);
-            disk.processUpdate(element);
-        }
-
-        // remove each
-        for (int i = 0; i < cnt; i++)
-        {
-            disk.remove("key:" + i);
-            ICacheElement<String, String> element = disk.processGet("key:" + i);
-            assertNull("Should not have received an element.", element);
-        }
-    }
-
-    /**
-     * Add some items to the disk cache and then remove them one by one.
-     * <p>
-     *
-     * @throws IOException
-     */
-    public void testRemove_PartialKey() throws IOException
-    {
-        BlockDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName("testRemove_PartialKey");
-        cattr.setMaxKeySize(100);
-        cattr.setDiskPath("target/test-sandbox/BlockDiskCacheUnitTest");
-        BlockDiskCache<String, String> disk = new BlockDiskCache<>(cattr);
-
-        disk.processRemoveAll();
-
-        int cnt = 25;
-        for (int i = 0; i < cnt; i++)
-        {
-            IElementAttributes eAttr = new ElementAttributes();
-            eAttr.setIsSpool(true);
-            ICacheElement<String, String> element = new CacheElement<>("testRemove_PartialKey", i + ":key", "data:"
-                + i);
-            element.setElementAttributes(eAttr);
-            disk.processUpdate(element);
-        }
-
-        // verify each
-        for (int i = 0; i < cnt; i++)
-        {
-            ICacheElement<String, String> element = disk.processGet(i + ":key");
-            assertNotNull("Shoulds have received an element.", element);
-        }
-
-        // remove each
-        for (int i = 0; i < cnt; i++)
-        {
-            disk.remove(i + ":");
-            ICacheElement<String, String> element = disk.processGet(i + ":key");
-            assertNull("Should not have received an element.", element);
-        }
-    }
-
-
-    /**
-     * Verify that group members are removed if we call remove with a group.
-     *
-     * @throws IOException
-     */
-    public void testRemove_Group() throws IOException
-    {
-        // SETUP
-        BlockDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName("testRemove_Group");
-        cattr.setMaxKeySize(100);
-        cattr.setDiskPath("target/test-sandbox/BlockDiskCacheUnitTest");
-        BlockDiskCache<GroupAttrName<String>, String> disk = new BlockDiskCache<>(cattr);
-
-        disk.processRemoveAll();
-
-        String cacheName = "testRemove_Group_Region";
-        String groupName = "testRemove_Group";
-
-        int cnt = 25;
-        for (int i = 0; i < cnt; i++)
-        {
-            GroupAttrName<String> groupAttrName = getGroupAttrName(cacheName, groupName, i + ":key");
-            CacheElement<GroupAttrName<String>, String> element = new CacheElement<>(cacheName,
-                groupAttrName, "data:" + i);
-
-            IElementAttributes eAttr = new ElementAttributes();
-            eAttr.setIsSpool(true);
-            element.setElementAttributes(eAttr);
-
-            disk.processUpdate(element);
-        }
-
-        // verify each
-        for (int i = 0; i < cnt; i++)
-        {
-            GroupAttrName<String> groupAttrName = getGroupAttrName(cacheName, groupName, i + ":key");
-            ICacheElement<GroupAttrName<String>, String> element = disk.processGet(groupAttrName);
-            assertNotNull("Should have received an element.", element);
-        }
-
-        // DO WORK
-        // remove the group
-        disk.remove(getGroupAttrName(cacheName, groupName, null));
-
-        for (int i = 0; i < cnt; i++)
-        {
-            GroupAttrName<String> groupAttrName = getGroupAttrName(cacheName, groupName, i + ":key");
-            ICacheElement<GroupAttrName<String>, String> element = disk.processGet(groupAttrName);
-
-            // VERIFY
-            assertNull("Should not have received an element.", element);
-        }
-
-    }
-
-    /**
-     * Internal method used for group functionality.
-     * <p>
-     *
-     * @param cacheName
-     * @param group
-     * @param name
-     * @return GroupAttrName
-     */
-    private GroupAttrName<String> getGroupAttrName(String cacheName, String group, String name)
-    {
-        GroupId gid = new GroupId(cacheName, group);
-        return new GroupAttrName<>(gid, name);
-    }
-
-    /** Holder for a string and byte array. */
-    static class X implements Serializable
-    {
-        /** ignore */
-        private static final long serialVersionUID = 1L;
-
-        /** Test string */
-        String string;
-
-        /*** test byte array. */
-        byte[] bytes;
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskUnitTest.java
deleted file mode 100644
index cf95502..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/block/BlockDiskUnitTest.java
+++ /dev/null
@@ -1,368 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.block;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Random;
-
-import org.apache.commons.jcs.utils.serialization.StandardSerializer;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-
-/**
- * Test for the disk access layer of the Block Disk Cache.
- * <p>
- * @author Aaron Smuts
- */
-public class BlockDiskUnitTest
-    extends TestCase
-{
-    /** data file. */
-    private File rafDir;
-    private BlockDisk disk;
-
-    /**
-     * @see junit.framework.TestCase#setUp()
-     * Creates the base directory
-     */
-    @Override
-    protected void setUp() throws Exception
-    {
-        super.setUp();
-        String rootDirName = "target/test-sandbox/block";
-        this.rafDir = new File( rootDirName );
-        this.rafDir.mkdirs();
-    }
-
-    private void setUpBlockDisk(String fileName) throws IOException
-    {
-        File file = new File(rafDir, fileName + ".data");
-        file.delete();
-        this.disk = new BlockDisk(file, new StandardSerializer());
-    }
-
-    private void setUpBlockDisk(String fileName, int blockSize) throws IOException
-    {
-        File file = new File(rafDir, fileName + ".data");
-        file.delete();
-        this.disk = new BlockDisk(file, blockSize, new StandardSerializer());
-    }
-
-    /**
-     * @see junit.framework.TestCase#tearDown()
-     */
-    @Override
-    protected void tearDown() throws Exception
-    {
-        disk.close();
-        super.tearDown();
-    }
-
-    /**
-     * Test writing a null object within a single block size.
-     * <p>
-     * @throws Exception
-     */
-    public void testWrite_NullBlockElement()
-        throws Exception
-    {
-        // SETUP
-        setUpBlockDisk("testWrite_NullBlockElement");
-
-        // DO WORK
-        int[] blocks = disk.write( null );
-
-        // VERIFY
-        assertEquals( "Wrong number of blocks recorded.", 1, disk.getNumberOfBlocks() );
-        assertEquals( "Wrong number of blocks returned.", 1, blocks.length );
-        assertEquals( "Wrong block returned.", 0, blocks[0] );
-    }
-
-    /**
-     * Test writing an element within a single block size.
-     * <p>
-     * @throws Exception
-     */
-    public void testWrite_SingleBlockElement()
-        throws Exception
-    {
-        // SETUP
-        setUpBlockDisk("testWrite_SingleBlockElement");
-
-        // DO WORK
-        int bytes = 1 * 1024;
-        int[] blocks = disk.write( new byte[bytes] );
-
-        // VERIFY
-        assertEquals( "Wrong number of blocks recorded.", 1, disk.getNumberOfBlocks() );
-        assertEquals( "Wrong number of blocks returned.", 1, blocks.length );
-        assertEquals( "Wrong block returned.", 0, blocks[0] );
-    }
-
-    /**
-     * Test writing and reading an element within a single block size.
-     * <p>
-     * @throws Exception
-     */
-    public void testWriteAndRead_SingleBlockElement()
-        throws Exception
-    {
-        // SETUP
-        setUpBlockDisk("testWriteAndRead_SingleBlockElement");
-
-        // DO WORK
-        int bytes = 1 * 1024;
-        int[] blocks = disk.write( new byte[bytes] );
-
-        byte[] result = (byte[]) disk.read( blocks );
-
-        // VERIFY
-        assertEquals( "Wrong item retured.", new byte[bytes].length, result.length );
-    }
-
-    /**
-     * Test writing two elements that each fit within a single block size.
-     * <p>
-     * @throws Exception
-     */
-    public void testWrite_TwoSingleBlockElements()
-        throws Exception
-    {
-        // SETUP
-        setUpBlockDisk("testWrite_TwoSingleBlockElements");
-
-        // DO WORK
-        int bytes = 1 * 1024;
-        int[] blocks1 = disk.write( new byte[bytes] );
-        int[] blocks2 = disk.write( new byte[bytes] );
-
-        // VERIFY
-        assertEquals( "Wrong number of blocks recorded.", 2, disk.getNumberOfBlocks() );
-        assertEquals( "Wrong number of blocks returned.", 1, blocks1.length );
-        assertEquals( "Wrong block returned.", 0, blocks1[0] );
-        assertEquals( "Wrong number of blocks returned.", 1, blocks2.length );
-        assertEquals( "Wrong block returned.", 1, blocks2[0] );
-    }
-
-    /**
-     * Verify that it says we need two blocks if the total size will fit.
-     * <p>
-     * @throws Exception
-     */
-    public void testCalculateBlocksNeededDouble()
-        throws Exception
-    {
-        // SETUP
-        setUpBlockDisk("testCalculateBlocksNeededDouble");
-
-        // DO WORK
-        int result = disk.calculateTheNumberOfBlocksNeeded( new byte[disk.getBlockSizeBytes() * 2
-            - ( 2 * BlockDisk.HEADER_SIZE_BYTES )] );
-
-        // Verify
-        assertEquals( "Wrong number of blocks", 2, result );
-    }
-
-    /**
-     * Test writing an element that takes two blocks.
-     * <p>
-     * @throws Exception
-     */
-    public void testWrite_DoubleBlockElement()
-        throws Exception
-    {
-        // SETUP
-        setUpBlockDisk("testWriteDoubleBlockElement");
-
-        // DO WORK
-        // byte arrays encur 27 bytes of serialization overhead.
-        int bytes = getBytesForBlocksOfByteArrays( disk.getBlockSizeBytes(), 2 );
-        int[] blocks = disk.write( new byte[bytes] );
-
-        // VERIFY
-        assertEquals( "Wrong number of blocks recorded.", 2, disk.getNumberOfBlocks() );
-        assertEquals( "Wrong number of blocks returned.", 2, blocks.length );
-        assertEquals( "Wrong block returned.", 0, blocks[0] );
-    }
-
-    /**
-     * Test writing an element that takes 128 blocks.  There was a byte in a for loop that limited the number to 127.  I fixed this.
-     * <p>
-     * @throws Exception
-     */
-    public void testWrite_128BlockElement()
-        throws Exception
-    {
-        // SETUP
-        int numBlocks = 128;
-
-        setUpBlockDisk("testWrite_128BlockElement");
-
-        // DO WORK
-        // byte arrays encur 27 bytes of serialization overhead.
-        int bytes = getBytesForBlocksOfByteArrays( disk.getBlockSizeBytes(), numBlocks );
-        int[] blocks = disk.write( new byte[bytes] );
-
-        // VERIFY
-        assertEquals( "Wrong number of blocks recorded.", numBlocks, disk.getNumberOfBlocks() );
-        assertEquals( "Wrong number of blocks returned.", numBlocks, blocks.length );
-        assertEquals( "Wrong block returned.", 0, blocks[0] );
-    }
-
-    /**
-     * Test writing and reading elements that do not fit within a single block.
-     * <p>
-     * @throws Exception
-     */
-    public void testWriteAndReadMultipleMultiBlockElement()
-        throws Exception
-    {
-        // SETUP
-        setUpBlockDisk("testWriteAndReadSingleBlockElement");
-
-        // DO WORK
-        int numBlocksPerElement = 4;
-        int bytes = getBytesForBlocksOfByteArrays( disk.getBlockSizeBytes(), numBlocksPerElement );
-
-        int numElements = 100;
-        for ( int i = 0; i < numElements; i++ )
-        {
-            int[] blocks = disk.write( new byte[bytes] );
-            byte[] result = (byte[]) disk.read( blocks );
-
-            // VERIFY
-            assertEquals( "Wrong item retured.", new byte[bytes].length, result.length );
-            assertEquals( "Wrong number of blocks returned.", numBlocksPerElement, blocks.length );
-        }
-    }
-
-    /**
-     * Test writing and reading elements that do not fit within a single block.
-     * <p>
-     * @throws Exception
-     */
-    public void testWriteAndReadMultipleMultiBlockElement_setSize()
-        throws Exception
-    {
-        // SETUP
-        setUpBlockDisk("testWriteAndReadSingleBlockElement", 1024);
-
-        // DO WORK
-        int numBlocksPerElement = 4;
-        int bytes = getBytesForBlocksOfByteArrays( disk.getBlockSizeBytes(), numBlocksPerElement );
-
-        int numElements = 100;
-        Random r = new Random(System.currentTimeMillis());
-        final byte[] src = new byte[bytes];
-        for ( int i = 0; i < numElements; i++ )
-        {
-            r.nextBytes(src);  // Ensure we don't just write zeros out
-            int[] blocks = disk.write( src );
-            byte[] result = (byte[]) disk.read( blocks );
-
-            // VERIFY
-            assertEquals( "Wrong item length retured.", src.length, result.length );
-            assertEquals( "Wrong number of blocks returned.", numBlocksPerElement, blocks.length );
-
-            // We check the array contents, too, to ensure we read back what we wrote out
-            for (int j = 0 ; j < src.length ; j++) {
-                assertEquals( "Mismatch at offset " + j + " in attempt # " + (i + 1), src[j], result[j] );
-            }
-        }
-        assertEquals( "Wrong number of elements. "+disk, numBlocksPerElement * numElements, disk.getNumberOfBlocks() );
-    }
-
-    /**
-     * Used to get the size for byte arrays that will take up the number of blocks specified.
-     * <p>
-     * @param blockSize
-     * @param numBlocks
-     * @return num bytes.
-     */
-    private int getBytesForBlocksOfByteArrays( int blockSize, int numBlocks )
-    {
-        // byte arrays encur some bytes of serialization overhead.
-        return blockSize * numBlocks - ( numBlocks * BlockDisk.HEADER_SIZE_BYTES ) - ( numBlocks * 14 );
-    }
-
-    /**
-     * Verify that the block disk can handle a big string.
-     * <p>
-     * @throws Exception
-     */
-    public void testWriteAndRead_BigString()
-        throws Exception
-    {
-        // SETUP
-        setUpBlockDisk("testWriteAndRead_BigString", 4096); //1024
-
-        String string = "This is my big string ABCDEFGH";
-        StringBuilder sb = new StringBuilder();
-        sb.append( string );
-        for ( int i = 0; i < 8; i++ )
-        {
-            sb.append( " " + i + sb.toString() ); // big string
-        }
-        string = sb.toString();
-
-        // DO WORK
-        int[] blocks = disk.write( string );
-        String result = (String) disk.read( blocks );
-
-        // VERIFY
-//        System.out.println( string );
-//        System.out.println( result );
-//        System.out.println( disk );
-        assertEquals( "Wrong item retured.", string, result );
-    }
-
-    /**
-     * Verify that the block disk can handle a big string.
-     * <p>
-     * @throws Exception
-     */
-    public void testWriteAndRead_BigString2()
-        throws Exception
-    {
-        // SETUP
-        setUpBlockDisk("testWriteAndRead_BigString", 47); //4096;//1024
-
-        String string = "abcdefghijklmnopqrstuvwxyz1234567890";
-        string += string;
-        string += string;
-
-        // DO WORK
-        int[] blocks = disk.write( string );
-        String result = (String) disk.read( blocks );
-
-        // VERIFY
-        assertEquals( "Wrong item retured.", string, result );
-    }
-
-    public void testJCS156() throws Exception
-    {
-        // SETUP
-        setUpBlockDisk("testJCS156", 4096);
-        long offset = disk.calculateByteOffsetForBlockAsLong(Integer.MAX_VALUE);
-        assertTrue("Must not wrap round", offset > 0);
-        assertEquals(Integer.MAX_VALUE*4096L,offset);
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/block/HugeQuantityBlockDiskCacheLoadTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/block/HugeQuantityBlockDiskCacheLoadTest.java
deleted file mode 100644
index a22a449..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/block/HugeQuantityBlockDiskCacheLoadTest.java
+++ /dev/null
@@ -1,135 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.block;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.utils.timing.ElapsedTimer;
-import org.apache.commons.jcs.utils.timing.SleepUtil;
-
-/**
- * Put a few hundred thousand entries in the block disk cache.
- * @author Aaron Smuts
- */
-public class HugeQuantityBlockDiskCacheLoadTest
-    extends TestCase
-{
-
-    /**
-     * Test setup
-     */
-    @Override
-    public void setUp()
-    {
-        JCS.setConfigFilename( "/TestBlockDiskCacheHuge.ccf" );
-    }
-
-    /**
-     * Adds items to cache, gets them, and removes them. The item count is more than the size of the
-     * memory cache, so items should spool to disk.
-     * <p>
-     * @throws Exception If an error occurs
-     */
-    public void testLargeNumberOfItems()
-        throws Exception
-    {
-        int items = 300000;
-        String region = "testCache1";
-
-        System.out.println( "--------------------------" );
-        long initialMemory = measureMemoryUse();
-        System.out.println( "Before getting JCS: " + initialMemory );
-
-        CacheAccess<String, String> jcs = JCS.getInstance( region );
-        jcs.clear();
-
-        try
-        {
-            ElapsedTimer timer = new ElapsedTimer();
-            System.out.println( "Start: " + measureMemoryUse() );
-
-            // Add items to cache
-            for ( int i = 0; i <= items; i++ )
-            {
-                jcs.put( i + ":key", region + " data " + i );
-            }
-
-            System.out.println( jcs.getStats() );
-            System.out.println( "--------------------------" );
-            System.out.println( "After put: " + measureMemoryUse() );
-
-            Thread.sleep( 5000 );
-
-            System.out.println( jcs.getStats() );
-            System.out.println( "--------------------------" );
-            System.out.println( "After wait: " + measureMemoryUse() );
-
-            for ( int i = 0; i < 10; i++ )
-            {
-                SleepUtil.sleepAtLeast( 3000 );
-                System.out.println( "--------------------------" );
-                System.out.println( "After sleep. " + timer.getElapsedTimeString() + " memory used = "
-                    + measureMemoryUse() );
-                System.out.println( jcs.getStats() );
-            }
-
-            // Test that all items are in cache
-            System.out.println( "--------------------------" );
-            System.out.println( "Retrieving all." );
-            for ( int i = 0; i <= items; i++ )
-            {
-                //System.out.print(  "\033[s" );
-                String value = jcs.get( i + ":key" );
-                if ( i % 1000 == 0 )
-                {
-                    //System.out.print(  "\033[r" );
-                    System.out.println( i + " " );
-                }
-                assertEquals( "Wrong value returned.", region + " data " + i, value );
-            }
-            long aftetGet = measureMemoryUse();
-            System.out.println( "After get: " + aftetGet + " diff = " + ( aftetGet - initialMemory ) );
-
-        }
-        finally
-        {
-            // dump the stats to the report
-            System.out.println( jcs.getStats() );
-            System.out.println( "--------------------------" );
-            long endMemory = measureMemoryUse();
-            System.out.println( "End: " + endMemory + " diff = " + ( endMemory - initialMemory ) );
-        }
-    }
-
-    /**
-     * Measure memory used by the VM.
-     * @return long
-     * @throws InterruptedException
-     */
-    protected long measureMemoryUse()
-        throws InterruptedException
-    {
-        System.gc();
-        Thread.sleep( 3000 );
-        System.gc();
-        return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/DiskTestObjectUtil.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/DiskTestObjectUtil.java
deleted file mode 100644
index eca8864..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/DiskTestObjectUtil.java
+++ /dev/null
@@ -1,143 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.indexed;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.util.Random;
-
-import org.apache.commons.jcs.auxiliary.disk.DiskTestObject;
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.utils.serialization.StandardSerializer;
-
-/**
- * Utility for dealing with test objects.
- * <p>
- * @author Aaron Smuts
- */
-public class DiskTestObjectUtil
-{
-    /**
-     * Total from the start to the endPostion.
-     * <p>
-     * @param testObjects
-     * @param endPosition
-     * @return size
-     * @throws IOException
-     */
-    public static long totalSize( DiskTestObject[] testObjects, int endPosition )
-        throws IOException
-    {
-        StandardSerializer serializer = new StandardSerializer();
-        long total = 0;
-        for ( int i = 0; i < endPosition; i++ )
-        {
-            int tileSize = serializer.serialize( testObjects[i] ).length + IndexedDisk.HEADER_SIZE_BYTES;
-            total += tileSize;
-        }
-        return total;
-    }
-
-    /**
-     * Total from the start to the endPostion.
-     * <p>
-     * @param elements
-     * @param endPosition
-     * @return size
-     * @throws IOException
-     */
-    public static <K, V> long totalSize( ICacheElement<K, V>[] elements, int endPosition )
-        throws IOException
-    {
-        return totalSize( elements, 0, endPosition );
-    }
-
-    /**
-     * Total from the start to the endPostion.
-     * <p>
-     * @param elements
-     * @param startPosition
-     * @param endPosition
-     * @return size
-     * @throws IOException
-     */
-    public static <K, V> long totalSize( ICacheElement<K, V>[] elements, int startPosition, int endPosition )
-        throws IOException
-    {
-        StandardSerializer serializer = new StandardSerializer();
-        long total = 0;
-        for ( int i = startPosition; i < endPosition; i++ )
-        {
-            int tileSize = serializer.serialize( elements[i] ).length + IndexedDisk.HEADER_SIZE_BYTES;
-            total += tileSize;
-        }
-        return total;
-    }
-
-    /**
-     * Creates an array of ICacheElements with DiskTestObjects with payloads the byte size.
-     * <p>
-     * @param numToCreate
-     * @param bytes
-     * @param cacheName
-     * @return ICacheElement[]
-     */
-    public static ICacheElement<Integer, DiskTestObject>[] createCacheElementsWithTestObjects( int numToCreate, int bytes, String cacheName )
-    {
-        @SuppressWarnings("unchecked")
-        ICacheElement<Integer, DiskTestObject>[] elements = new ICacheElement[numToCreate];
-        for ( int i = 0; i < numToCreate; i++ )
-        {
-            // 24 KB
-            int size = bytes * 1024;
-            DiskTestObject tile = new DiskTestObject( Integer.valueOf( i ), new byte[size] );
-
-            ICacheElement<Integer, DiskTestObject> element = new CacheElement<>( cacheName, tile.id, tile );
-            elements[i] = element;
-        }
-        return elements;
-    }
-
-    /**
-     * Creates an array of ICacheElements with DiskTestObjects with payloads the byte size.
-     * <p>
-     * @param numToCreate
-     * @param cacheName
-     * @return ICacheElement[]
-     */
-    public static ICacheElement<Integer, DiskTestObject>[] createCacheElementsWithTestObjectsOfVariableSizes( int numToCreate, String cacheName )
-    {
-        @SuppressWarnings("unchecked")
-        ICacheElement<Integer, DiskTestObject>[] elements = new ICacheElement[numToCreate];
-        Random random = new Random( 89 );
-        for ( int i = 0; i < numToCreate; i++ )
-        {
-            int bytes = random.nextInt( 20 );
-            // 4-24 KB
-            int size = ( bytes + 4 ) * 1024;
-            DiskTestObject tile = new DiskTestObject( Integer.valueOf( i ), new byte[size] );
-
-            ICacheElement<Integer, DiskTestObject> element = new CacheElement<>( cacheName, tile.id, tile );
-            elements[i] = element;
-        }
-        return elements;
-    }
-
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/HugeQuantityIndDiskCacheLoadTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/HugeQuantityIndDiskCacheLoadTest.java
deleted file mode 100644
index fc082cb..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/HugeQuantityIndDiskCacheLoadTest.java
+++ /dev/null
@@ -1,124 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.indexed;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-
-/**
- * Put a few hundred thousand entries in the disk cache.
- * <p>
- * @author Aaron Smuts
- */
-public class HugeQuantityIndDiskCacheLoadTest
-    extends TestCase
-{
-    /** Test setup.  */
-    @Override
-    public void setUp()
-    {
-        JCS.setConfigFilename( "/TestDiskCacheHuge.ccf" );
-    }
-
-    /**
-     * Adds items to cache, gets them, and removes them. The item count is more than the size of the
-     * memory cache, so items should spool to disk.
-     * <p>
-     * @throws Exception If an error occurs
-     */
-    public void testLargeNumberOfItems()
-        throws Exception
-    {
-        int items = 300000;
-        String region = "testCache1";
-
-        CacheAccess<String, String> jcs = JCS.getInstance( region );
-
-        try
-        {
-            System.out.println( "Start: " + measureMemoryUse() );
-
-            // Add items to cache
-
-            for ( int i = 0; i <= items; i++ )
-            {
-                jcs.put( i + ":key", region + " data " + i );
-            }
-
-            System.out.println( jcs.getStats() );
-            System.out.println( "--------------------------" );
-            System.out.println( "After put: " + measureMemoryUse() );
-
-            Thread.sleep( 5000 );
-
-            System.out.println( jcs.getStats() );
-            System.out.println( "--------------------------" );
-            System.out.println( "After wait: " + measureMemoryUse() );
-
-            // Test that all items are in cache
-
-            for ( int i = 0; i <= items; i++ )
-            {
-                String value = jcs.get( i + ":key" );
-
-                assertEquals( region + " data " + i, value );
-            }
-
-            System.out.println( "After get: " + measureMemoryUse() );
-
-            // // Remove all the items
-            // for ( int i = 0; i <= items; i++ )
-            // {
-            // jcs.remove( i + ":key" );
-            // }
-            //
-            // // Verify removal
-            // for ( int i = 0; i <= items; i++ )
-            // {
-            // assertNull( "Removed key should be null: " + i + ":key" + "\n
-            // stats " + jcs.getStats(), jcs.get( i + ":key" ) );
-            // }
-
-        }
-        finally
-        {
-            // dump the stats to the report
-            System.out.println( jcs.getStats() );
-            System.out.println( "--------------------------" );
-            System.out.println( "End: " + measureMemoryUse() );
-        }
-    }
-
-    /**
-     * Measure memory used by the VM.
-     * <p>
-     * @return memory used
-     * @throws InterruptedException
-     */
-    protected long measureMemoryUse()
-        throws InterruptedException
-    {
-        System.gc();
-        Thread.sleep( 3000 );
-        System.gc();
-        return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexDiskCacheCountUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexDiskCacheCountUnitTest.java
deleted file mode 100644
index ef8eea3..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexDiskCacheCountUnitTest.java
+++ /dev/null
@@ -1,109 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.indexed;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-
-import org.apache.commons.jcs.auxiliary.disk.behavior.IDiskCacheAttributes.DiskLimitType;
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-
-public class IndexDiskCacheCountUnitTest extends IndexDiskCacheUnitTestAbstract {
-
-	@Override
-	public IndexedDiskCacheAttributes getCacheAttributes() {
-		IndexedDiskCacheAttributes ret = new IndexedDiskCacheAttributes();
-		ret.setDiskLimitType(DiskLimitType.COUNT);
-		return ret;
-	}
-	  public void testRecycleBin()
-		        throws IOException
-		    {
-		        IndexedDiskCacheAttributes cattr = getCacheAttributes();
-		        cattr.setCacheName( "testRemoveItems" );
-		        cattr.setOptimizeAtRemoveCount( 7 );
-		        cattr.setMaxKeySize( 5 );
-		        cattr.setMaxPurgatorySize( 0 );
-		        cattr.setDiskPath( "target/test-sandbox/BreakIndexTest" );
-		        IndexedDiskCache<String, String> disk = new IndexedDiskCache<>( cattr );
-
-		        String[] test = { "a", "bb", "ccc", "dddd", "eeeee", "ffffff", "ggggggg", "hhhhhhhhh", "iiiiiiiiii" };
-		        String[] expect = { null, "bb", "ccc", null, null, "ffffff", null, "hhhhhhhhh", "iiiiiiiiii" };
-
-		        //System.out.println( "------------------------- testRecycleBin " );
-
-		        for ( int i = 0; i < 6; i++ )
-		        {
-		            ICacheElement<String, String> element = new CacheElement<>( "testRecycleBin", "key:" + test[i], test[i] );
-		            //System.out.println( "About to add " + "key:" + test[i] + " i = " + i );
-		            disk.processUpdate( element );
-		        }
-
-		        for ( int i = 3; i < 5; i++ )
-		        {
-		            //System.out.println( "About to remove " + "key:" + test[i] + " i = " + i );
-		            disk.remove( "key:" + test[i] );
-		        }
-
-		        // there was a bug where 7 would try to be put in the empty slot left by 4's removal, but it
-		        // will not fit.
-		        for ( int i = 7; i < 9; i++ )
-		        {
-		            ICacheElement<String, String> element = new CacheElement<>( "testRecycleBin", "key:" + test[i], test[i] );
-		            //System.out.println( "About to add " + "key:" + test[i] + " i = " + i );
-		            disk.processUpdate( element );
-		        }
-
-		        try
-		        {
-		            for ( int i = 0; i < 9; i++ )
-		            {
-		                ICacheElement<String, String> element = disk.get( "key:" + test[i] );
-		                if ( element != null )
-		                {
-		                    //System.out.println( "element = " + element.getVal() );
-		                }
-		                else
-		                {
-		                    //System.out.println( "null --" + "key:" + test[i] );
-		                }
-
-		                String expectedValue = expect[i];
-		                if ( expectedValue == null )
-		                {
-		                    assertNull( "Expected a null element", element );
-		                }
-		                else
-		                {
-		                    assertNotNull( "The element for key [" + "key:" + test[i] + "] should not be null. i = " + i,
-		                                   element );
-		                    assertEquals( "Elements contents do not match expected", element.getVal(), expectedValue );
-		                }
-		            }
-		        }
-		        catch ( Exception e )
-		        {
-		            e.printStackTrace();
-		            fail( "Should not get an exception: " + e.toString() );
-		        }
-
-		        disk.removeAll();
-		    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexDiskCacheSizeUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexDiskCacheSizeUnitTest.java
deleted file mode 100644
index 1a3456b..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexDiskCacheSizeUnitTest.java
+++ /dev/null
@@ -1,110 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.indexed;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-
-import org.apache.commons.jcs.auxiliary.disk.DiskTestObject;
-import org.apache.commons.jcs.auxiliary.disk.behavior.IDiskCacheAttributes.DiskLimitType;
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-
-public class IndexDiskCacheSizeUnitTest extends IndexDiskCacheUnitTestAbstract {
-
-	@Override
-	public IndexedDiskCacheAttributes getCacheAttributes() {
-		IndexedDiskCacheAttributes ret = new IndexedDiskCacheAttributes();
-		ret.setDiskLimitType(DiskLimitType.SIZE);
-		return ret;
-	}
-	  public void testRecycleBin()
-		        throws IOException
-		    {
-		        IndexedDiskCacheAttributes cattr = getCacheAttributes();
-		        cattr.setCacheName( "testRemoveItems" );
-		        cattr.setOptimizeAtRemoveCount( 7 );
-		        cattr.setMaxKeySize( 8); // 1kb DiskTestObject takes 1420 bytes, so 5*1420 = 7100, so to keep 5 ojbects, we need max key size of 8
-		        cattr.setMaxPurgatorySize( 0 );
-		        cattr.setDiskPath( "target/test-sandbox/BreakIndexTest" );
-		        IndexedDiskCache<String, DiskTestObject> disk = new IndexedDiskCache<>( cattr );
-
-		        String[] test = { "a", "bb", "ccc", "dddd", "eeeee", "ffffff", "ggggggg", "hhhhhhhhh", "iiiiiiiiii" };
-		        String[] expect = { null, "bb", "ccc", null, null, "ffffff", null, "hhhhhhhhh", "iiiiiiiiii" };
-		        DiskTestObject value = DiskTestObjectUtil.createCacheElementsWithTestObjects( 1, 1, cattr .getCacheName())[0].getVal();
-		        //System.out.println( "------------------------- testRecycleBin " );
-
-		        for ( int i = 0; i < 6; i++ )
-		        {
-		            ICacheElement<String, DiskTestObject> element = new CacheElement<>( "testRecycleBin", "key:" + test[i], value);
-		            //System.out.println( "About to add " + "key:" + test[i] + " i = " + i );
-		            disk.processUpdate( element );
-		        }
-
-		        for ( int i = 3; i < 5; i++ )
-		        {
-		            //System.out.println( "About to remove " + "key:" + test[i] + " i = " + i );
-		            disk.remove( "key:" + test[i] );
-		        }
-
-		        // there was a bug where 7 would try to be put in the empty slot left by 4's removal, but it
-		        // will not fit.
-		        for ( int i = 7; i < 9; i++ )
-		        {
-		            ICacheElement<String, DiskTestObject> element = new CacheElement<>( "testRecycleBin", "key:" + test[i], value);
-		            //System.out.println( "About to add " + "key:" + test[i] + " i = " + i );
-		            disk.processUpdate( element );
-		        }
-
-		        try
-		        {
-		            for ( int i = 0; i < 9; i++ )
-		            {
-		                ICacheElement<String, DiskTestObject> element = disk.get( "key:" + test[i] );
-		                if ( element != null )
-		                {
-		                    //System.out.println( "element = " + element.getVal() );
-		                }
-		                else
-		                {
-		                    //System.out.println( "null --" + "key:" + test[i] );
-		                }
-
-		                String expectedValue = expect[i];
-		                if ( expectedValue == null )
-		                {
-		                    assertNull( "Expected a null element", element );
-		                }
-		                else
-		                {
-		                    assertNotNull( "The element for key [" + "key:" + test[i] + "] should not be null. i = " + i,
-		                                   element );
-		                    assertEquals( "Elements contents do not match expected", element.getVal(), value );
-		                }
-		            }
-		        }
-		        catch ( Exception e )
-		        {
-		            e.printStackTrace();
-		            fail( "Should not get an exception: " + e.toString() );
-		        }
-
-		        disk.removeAll();
-		    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexDiskCacheUnitTestAbstract.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexDiskCacheUnitTestAbstract.java
deleted file mode 100644
index 3bc4eb8..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexDiskCacheUnitTestAbstract.java
+++ /dev/null
@@ -1,989 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.indexed;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.commons.jcs.auxiliary.MockCacheEventLogger;
-import org.apache.commons.jcs.auxiliary.disk.DiskTestObject;
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.ElementAttributes;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-import org.apache.commons.jcs.engine.control.group.GroupAttrName;
-import org.apache.commons.jcs.engine.control.group.GroupId;
-import org.apache.commons.jcs.utils.timing.SleepUtil;
-
-import junit.framework.TestCase;
-
-/**
- * Tests for common functionality.
- * <p>
- *
- * @author Aaron Smuts
- */
-public abstract class IndexDiskCacheUnitTestAbstract extends TestCase
-{
-    public abstract IndexedDiskCacheAttributes getCacheAttributes();
-
-    /**
-     * Simply verify that we can put items in the disk cache and retrieve them.
-     *
-     * @throws IOException
-     */
-    public void testSimplePutAndGet() throws IOException
-    {
-        IndexedDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName("testSimplePutAndGet");
-        cattr.setMaxKeySize(1000);
-        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTest");
-        IndexedDiskCache<String, String> disk = new IndexedDiskCache<>(cattr);
-
-        disk.processRemoveAll();
-
-        int cnt = 999;
-        for (int i = 0; i < cnt; i++)
-        {
-            IElementAttributes eAttr = new ElementAttributes();
-            eAttr.setIsSpool(true);
-            ICacheElement<String, String> element = new CacheElement<>("testSimplePutAndGet", "key:" + i, "data:" + i);
-            element.setElementAttributes(eAttr);
-            disk.processUpdate(element);
-        }
-
-        for (int i = 0; i < cnt; i++)
-        {
-            ICacheElement<String, String> element = disk.processGet("key:" + i);
-            assertNotNull("Should have received an element.", element);
-            assertEquals("Element is wrong.", "data:" + i, element.getVal());
-        }
-
-        // Test that getMultiple returns all the expected values
-        Set<String> keys = new HashSet<>();
-        for (int i = 0; i < cnt; i++)
-        {
-            keys.add("key:" + i);
-        }
-
-        Map<String, ICacheElement<String, String>> elements = disk.getMultiple(keys);
-        for (int i = 0; i < cnt; i++)
-        {
-            ICacheElement<String, String> element = elements.get("key:" + i);
-            assertNotNull("element " + i + ":key is missing", element);
-            assertEquals("value key:" + i, "data:" + i, element.getVal());
-        }
-        // System.out.println( disk.getStats() );
-    }
-
-    /**
-     * Add some items to the disk cache and then remove them one by one.
-     *
-     * @throws IOException
-     */
-    public void testRemoveItems() throws IOException
-    {
-        IndexedDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName("testRemoveItems");
-        cattr.setMaxKeySize(100);
-        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTest");
-        IndexedDiskCache<String, String> disk = new IndexedDiskCache<>(cattr);
-
-        disk.processRemoveAll();
-
-        int cnt = 25;
-        for (int i = 0; i < cnt; i++)
-        {
-            IElementAttributes eAttr = new ElementAttributes();
-            eAttr.setIsSpool(true);
-            ICacheElement<String, String> element = new CacheElement<>("testRemoveItems", "key:" + i, "data:" + i);
-            element.setElementAttributes(eAttr);
-            disk.processUpdate(element);
-        }
-
-        // remove each
-        for (int i = 0; i < cnt; i++)
-        {
-            disk.remove("key:" + i);
-            ICacheElement<String, String> element = disk.processGet("key:" + i);
-            assertNull("Should not have received an element.", element);
-        }
-    }
-
-    /**
-     * Verify that we don't override the largest item.
-     * <p>
-     *
-     * @throws IOException
-     */
-
-    /**
-     * Verify that the overlap check returns true when there are no overlaps.
-     */
-    public void testCheckForDedOverlaps_noOverlap()
-    {
-        // SETUP
-        IndexedDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName("testCheckForDedOverlaps_noOverlap");
-        cattr.setDiskPath("target/test-sandbox/UnitTest");
-        IndexedDiskCache<String, String> disk = new IndexedDiskCache<>(cattr);
-
-        int numDescriptors = 5;
-        int pos = 0;
-        IndexedDiskElementDescriptor[] sortedDescriptors = new IndexedDiskElementDescriptor[numDescriptors];
-        for (int i = 0; i < numDescriptors; i++)
-        {
-            IndexedDiskElementDescriptor descriptor = new IndexedDiskElementDescriptor(pos, i * 2);
-            pos = pos + (i * 2) + IndexedDisk.HEADER_SIZE_BYTES;
-            sortedDescriptors[i] = descriptor;
-        }
-
-        // DO WORK
-        boolean result = disk.checkForDedOverlaps(sortedDescriptors);
-
-        // VERIFY
-        assertTrue("There should be no overlap. it should be ok", result);
-    }
-
-    /**
-     * Verify that the overlap check returns false when there are overlaps.
-     */
-    public void testCheckForDedOverlaps_overlaps()
-    {
-        // SETUP
-        IndexedDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName("testCheckForDedOverlaps_overlaps");
-        cattr.setDiskPath("target/test-sandbox/UnitTest");
-        IndexedDiskCache<String, String> disk = new IndexedDiskCache<>(cattr);
-
-        int numDescriptors = 5;
-        int pos = 0;
-        IndexedDiskElementDescriptor[] sortedDescriptors = new IndexedDiskElementDescriptor[numDescriptors];
-        for (int i = 0; i < numDescriptors; i++)
-        {
-            IndexedDiskElementDescriptor descriptor = new IndexedDiskElementDescriptor(pos, i * 2);
-            // don't add the header + IndexedDisk.RECORD_HEADER;
-            pos = pos + (i * 2);
-            sortedDescriptors[i] = descriptor;
-        }
-
-        // DO WORK
-        boolean result = disk.checkForDedOverlaps(sortedDescriptors);
-
-        // VERIFY
-        assertFalse("There should be overlaps. it should be not ok", result);
-    }
-
-    /**
-     * Verify that the file size is as expected.
-     * <p>
-     *
-     * @throws IOException
-     * @throws InterruptedException
-     */
-    public void testFileSize() throws IOException, InterruptedException
-    {
-        // SETUP
-        IndexedDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName("testFileSize");
-        cattr.setDiskPath("target/test-sandbox/UnitTest");
-        IndexedDiskCache<Integer, DiskTestObject> disk = new IndexedDiskCache<>(cattr);
-
-        int numberToInsert = 20;
-        int bytes = 24;
-        ICacheElement<Integer, DiskTestObject>[] elements = DiskTestObjectUtil.createCacheElementsWithTestObjects(numberToInsert,
-            bytes, cattr.getCacheName());
-
-        for (int i = 0; i < elements.length; i++)
-        {
-            disk.processUpdate(elements[i]);
-        }
-
-        Thread.yield();
-        Thread.sleep(100);
-        Thread.yield();
-
-        long expectedSize = DiskTestObjectUtil.totalSize(elements, numberToInsert);
-        long resultSize = disk.getDataFileSize();
-
-        // System.out.println( "testFileSize stats " + disk.getStats() );
-
-        assertEquals("Wrong file size", expectedSize, resultSize);
-    }
-
-    /**
-     * Verify that items are added to the recycle bin on removal.
-     * <p>
-     *
-     * @throws IOException
-     * @throws InterruptedException
-     */
-    public void testRecyleBinSize() throws IOException, InterruptedException
-    {
-        // SETUP
-        int numberToInsert = 20;
-
-        IndexedDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName("testRecyleBinSize");
-        cattr.setDiskPath("target/test-sandbox/UnitTest");
-        cattr.setOptimizeAtRemoveCount(numberToInsert);
-        cattr.setMaxKeySize(numberToInsert * 2);
-        cattr.setMaxPurgatorySize(numberToInsert);
-        IndexedDiskCache<Integer, DiskTestObject> disk = new IndexedDiskCache<>(cattr);
-
-        int bytes = 1;
-        ICacheElement<Integer, DiskTestObject>[] elements = DiskTestObjectUtil.createCacheElementsWithTestObjects(numberToInsert,
-            bytes, cattr.getCacheName());
-
-        for (int i = 0; i < elements.length; i++)
-        {
-            disk.processUpdate(elements[i]);
-        }
-
-        Thread.yield();
-        Thread.sleep(100);
-        Thread.yield();
-
-        // remove half
-        int numberToRemove = elements.length / 2;
-        for (int i = 0; i < numberToRemove; i++)
-        {
-            disk.processRemove(elements[i].getKey());
-        }
-
-        // verify that the recycle bin has the correct amount.
-        assertEquals("The recycle bin should have the number removed.", numberToRemove, disk.getRecyleBinSize());
-    }
-
-    /**
-     * Verify that items of the same size use recycle bin spots. Setup the recycle bin by removing
-     * some items. Add some of the same size. Verify that the recycle count is the number added.
-     * <p>
-     *
-     * @throws IOException
-     * @throws InterruptedException
-     */
-    public void testRecyleBinUsage() throws IOException, InterruptedException
-    {
-        // SETUP
-        int numberToInsert = 20;
-
-        IndexedDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName("testRecyleBinUsage");
-        cattr.setDiskPath("target/test-sandbox/UnitTest");
-        cattr.setOptimizeAtRemoveCount(numberToInsert);
-        cattr.setMaxKeySize(numberToInsert * 2);
-        cattr.setMaxPurgatorySize(numberToInsert);
-        IndexedDiskCache<Integer, DiskTestObject> disk = new IndexedDiskCache<>(cattr);
-
-        // we will reuse these
-        int bytes = 1;
-        ICacheElement<Integer, DiskTestObject>[] elements = DiskTestObjectUtil.createCacheElementsWithTestObjects(numberToInsert,
-            bytes, cattr.getCacheName());
-
-        // Add some to the disk
-        for (int i = 0; i < elements.length; i++)
-        {
-            disk.processUpdate(elements[i]);
-        }
-
-        Thread.yield();
-        Thread.sleep(100);
-        Thread.yield();
-
-        // remove half of those added
-        int numberToRemove = elements.length / 2;
-        for (int i = 0; i < numberToRemove; i++)
-        {
-            disk.processRemove(elements[i].getKey());
-        }
-
-        // verify that the recycle bin has the correct amount.
-        assertEquals("The recycle bin should have the number removed.", numberToRemove, disk.getRecyleBinSize());
-
-        // add half as many as we removed. These should all use spots in the recycle bin.
-        int numberToAdd = numberToRemove / 2;
-        for (int i = 0; i < numberToAdd; i++)
-        {
-            disk.processUpdate(elements[i]);
-        }
-
-        // verify that we used the correct number of spots
-        assertEquals("The recycle bin should have the number removed." + disk.getStats(), numberToAdd, disk.getRecyleCount());
-    }
-
-    /**
-     * Verify that the data size is as expected after a remove and after a put that should use the
-     * spots.
-     * <p>
-     *
-     * @throws IOException
-     * @throws InterruptedException
-     */
-    public void testBytesFreeSize() throws IOException, InterruptedException
-    {
-        // SETUP
-        IndexedDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName("testBytesFreeSize");
-        cattr.setDiskPath("target/test-sandbox/UnitTest");
-        IndexedDiskCache<Integer, DiskTestObject> disk = new IndexedDiskCache<>(cattr);
-
-        int numberToInsert = 20;
-        int bytes = 24;
-        ICacheElement<Integer, DiskTestObject>[] elements = DiskTestObjectUtil.createCacheElementsWithTestObjects(numberToInsert,
-            bytes, cattr.getCacheName());
-
-        for (int i = 0; i < elements.length; i++)
-        {
-            disk.processUpdate(elements[i]);
-        }
-
-        Thread.yield();
-        Thread.sleep(100);
-        Thread.yield();
-
-        // remove half of those added
-        int numberToRemove = elements.length / 2;
-        for (int i = 0; i < numberToRemove; i++)
-        {
-            disk.processRemove(elements[i].getKey());
-        }
-
-        long expectedSize = DiskTestObjectUtil.totalSize(elements, numberToRemove);
-        long resultSize = disk.getBytesFree();
-
-        // System.out.println( "testBytesFreeSize stats " + disk.getStats() );
-
-        assertEquals("Wrong bytes free size" + disk.getStats(), expectedSize, resultSize);
-
-        // add half as many as we removed. These should all use spots in the recycle bin.
-        int numberToAdd = numberToRemove / 2;
-        for (int i = 0; i < numberToAdd; i++)
-        {
-            disk.processUpdate(elements[i]);
-        }
-
-        long expectedSize2 = DiskTestObjectUtil.totalSize(elements, numberToAdd);
-        long resultSize2 = disk.getBytesFree();
-        assertEquals("Wrong bytes free size" + disk.getStats(), expectedSize2, resultSize2);
-    }
-
-    /**
-     * Add some items to the disk cache and then remove them one by one.
-     * <p>
-     *
-     * @throws IOException
-     */
-    public void testRemove_PartialKey() throws IOException
-    {
-        IndexedDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName("testRemove_PartialKey");
-        cattr.setMaxKeySize(100);
-        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTest");
-        IndexedDiskCache<String, String> disk = new IndexedDiskCache<>(cattr);
-
-        disk.processRemoveAll();
-
-        int cnt = 25;
-        for (int i = 0; i < cnt; i++)
-        {
-            IElementAttributes eAttr = new ElementAttributes();
-            eAttr.setIsSpool(true);
-            ICacheElement<String, String> element = new CacheElement<>("testRemove_PartialKey", i + ":key", "data:"
-                + i);
-            element.setElementAttributes(eAttr);
-            disk.processUpdate(element);
-        }
-
-        // verif each
-        for (int i = 0; i < cnt; i++)
-        {
-            ICacheElement<String, String> element = disk.processGet(i + ":key");
-            assertNotNull("Shoulds have received an element.", element);
-        }
-
-        // remove each
-        for (int i = 0; i < cnt; i++)
-        {
-            disk.remove(i + ":");
-            ICacheElement<String, String> element = disk.processGet(i + ":key");
-            assertNull("Should not have received an element.", element);
-        }
-        // https://issues.apache.org/jira/browse/JCS-67
-        assertEquals("Recylenbin should not have more elements than we removed. Check for JCS-67", cnt, disk.getRecyleBinSize());
-    }
-
-    /**
-     * Verify that group members are removed if we call remove with a group.
-     *
-     * @throws IOException
-     */
-    public void testRemove_Group() throws IOException
-    {
-        // SETUP
-        IndexedDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName("testRemove_Group");
-        cattr.setMaxKeySize(100);
-        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTest");
-        IndexedDiskCache<GroupAttrName<String>, String> disk = new IndexedDiskCache<>(cattr);
-
-        disk.processRemoveAll();
-
-        String cacheName = "testRemove_Group_Region";
-        String groupName = "testRemove_Group";
-
-        int cnt = 25;
-        for (int i = 0; i < cnt; i++)
-        {
-            GroupAttrName<String> groupAttrName = getGroupAttrName(cacheName, groupName, i + ":key");
-            CacheElement<GroupAttrName<String>, String> element = new CacheElement<>(cacheName,
-                groupAttrName, "data:" + i);
-
-            IElementAttributes eAttr = new ElementAttributes();
-            eAttr.setIsSpool(true);
-            element.setElementAttributes(eAttr);
-
-            disk.processUpdate(element);
-        }
-
-        // verify each
-        for (int i = 0; i < cnt; i++)
-        {
-            GroupAttrName<String> groupAttrName = getGroupAttrName(cacheName, groupName, i + ":key");
-            ICacheElement<GroupAttrName<String>, String> element = disk.processGet(groupAttrName);
-            assertNotNull("Should have received an element.", element);
-        }
-
-        // DO WORK
-        // remove the group
-        disk.remove(getGroupAttrName(cacheName, groupName, null));
-
-        for (int i = 0; i < cnt; i++)
-        {
-            GroupAttrName<String> groupAttrName = getGroupAttrName(cacheName, groupName, i + ":key");
-            ICacheElement<GroupAttrName<String>, String> element = disk.processGet(groupAttrName);
-
-            // VERIFY
-            assertNull("Should not have received an element.", element);
-        }
-
-    }
-
-    /**
-     * Internal method used for group functionality.
-     * <p>
-     *
-     * @param cacheName
-     * @param group
-     * @param name
-     * @return GroupAttrName
-     */
-    private GroupAttrName<String> getGroupAttrName(String cacheName, String group, String name)
-    {
-        GroupId gid = new GroupId(cacheName, group);
-        return new GroupAttrName<>(gid, name);
-    }
-
-    /**
-     * Verify event log calls.
-     * <p>
-     *
-     * @throws Exception
-     */
-    public void testUpdate_EventLogging_simple() throws Exception
-    {
-        // SETUP
-        IndexedDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName("testUpdate_EventLogging_simple");
-        cattr.setMaxKeySize(100);
-        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTestCEL");
-        IndexedDiskCache<String, String> diskCache = new IndexedDiskCache<>(cattr);
-        diskCache.processRemoveAll();
-
-        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
-        diskCache.setCacheEventLogger(cacheEventLogger);
-
-        ICacheElement<String, String> item = new CacheElement<>("region", "key", "value");
-
-        // DO WORK
-        diskCache.update(item);
-
-        SleepUtil.sleepAtLeast(200);
-
-        // VERIFY
-        assertEquals("Start should have been called.", 1, cacheEventLogger.startICacheEventCalls);
-        assertEquals("End should have been called.", 1, cacheEventLogger.endICacheEventCalls);
-    }
-
-    /**
-     * Verify event log calls.
-     * <p>
-     *
-     * @throws Exception
-     */
-    public void testGet_EventLogging_simple() throws Exception
-    {
-        // SETUP
-        IndexedDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName("testGet_EventLogging_simple");
-        cattr.setMaxKeySize(100);
-        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTestCEL");
-        IndexedDiskCache<String, String> diskCache = new IndexedDiskCache<>(cattr);
-        diskCache.processRemoveAll();
-
-        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
-        diskCache.setCacheEventLogger(cacheEventLogger);
-
-        // DO WORK
-        diskCache.get("key");
-
-        // VERIFY
-        assertEquals("Start should have been called.", 1, cacheEventLogger.startICacheEventCalls);
-        assertEquals("End should have been called.", 1, cacheEventLogger.endICacheEventCalls);
-    }
-
-    /**
-     * Verify event log calls.
-     * <p>
-     *
-     * @throws Exception
-     */
-    public void testGetMultiple_EventLogging_simple() throws Exception
-    {
-        // SETUP
-        IndexedDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName("testGetMultiple_EventLogging_simple");
-        cattr.setMaxKeySize(100);
-        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTestCEL");
-        IndexedDiskCache<String, String> diskCache = new IndexedDiskCache<>(cattr);
-        diskCache.processRemoveAll();
-
-        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
-        diskCache.setCacheEventLogger(cacheEventLogger);
-
-        Set<String> keys = new HashSet<>();
-        keys.add("junk");
-
-        // DO WORK
-        diskCache.getMultiple(keys);
-
-        // VERIFY
-        // 1 for get multiple and 1 for get.
-        assertEquals("Start should have been called.", 2, cacheEventLogger.startICacheEventCalls);
-        assertEquals("End should have been called.", 2, cacheEventLogger.endICacheEventCalls);
-    }
-
-    /**
-     * Verify event log calls.
-     * <p>
-     *
-     * @throws Exception
-     */
-    public void testRemove_EventLogging_simple() throws Exception
-    {
-        // SETUP
-        IndexedDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName("testRemoveAll_EventLogging_simple");
-        cattr.setMaxKeySize(100);
-        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTestCEL");
-        IndexedDiskCache<String, String> diskCache = new IndexedDiskCache<>(cattr);
-        diskCache.processRemoveAll();
-
-        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
-        diskCache.setCacheEventLogger(cacheEventLogger);
-
-        // DO WORK
-        diskCache.remove("key");
-
-        // VERIFY
-        assertEquals("Start should have been called.", 1, cacheEventLogger.startICacheEventCalls);
-        assertEquals("End should have been called.", 1, cacheEventLogger.endICacheEventCalls);
-    }
-
-    /**
-     * Verify event log calls.
-     * <p>
-     *
-     * @throws Exception
-     */
-    public void testRemoveAll_EventLogging_simple() throws Exception
-    {
-        // SETUP
-        IndexedDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName("testRemoveAll_EventLogging_simple");
-        cattr.setMaxKeySize(100);
-        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTestCEL");
-        IndexedDiskCache<String, String> diskCache = new IndexedDiskCache<>(cattr);
-        diskCache.processRemoveAll();
-
-        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
-        diskCache.setCacheEventLogger(cacheEventLogger);
-
-        // DO WORK
-        diskCache.remove("key");
-
-        // VERIFY
-        assertEquals("Start should have been called.", 1, cacheEventLogger.startICacheEventCalls);
-        assertEquals("End should have been called.", 1, cacheEventLogger.endICacheEventCalls);
-    }
-
-    /**
-     * Test the basic get matching.
-     * <p>
-     *
-     * @throws Exception
-     */
-    public void testPutGetMatching_SmallWait() throws Exception
-    {
-        // SETUP
-        int items = 200;
-
-        String cacheName = "testPutGetMatching_SmallWait";
-        IndexedDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName(cacheName);
-        cattr.setMaxKeySize(100);
-        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTest");
-        IndexedDiskCache<String, String> diskCache = new IndexedDiskCache<>(cattr);
-
-        // DO WORK
-        for (int i = 0; i <= items; i++)
-        {
-            diskCache.update(new CacheElement<>(cacheName, i + ":key", cacheName + " data " + i));
-        }
-        Thread.sleep(500);
-
-        Map<String, ICacheElement<String, String>> matchingResults = diskCache.getMatching("1.8.+");
-
-        // VERIFY
-        assertEquals("Wrong number returned", 10, matchingResults.size());
-        // System.out.println( "matchingResults.keySet() " + matchingResults.keySet() );
-        // System.out.println( "\nAFTER TEST \n" + diskCache.getStats() );
-    }
-
-    /**
-     * Test the basic get matching. With no wait this will all come from purgatory.
-     * <p>
-     *
-     * @throws Exception
-     */
-    public void testPutGetMatching_NoWait() throws Exception
-    {
-        // SETUP
-        int items = 200;
-
-        String cacheName = "testPutGetMatching_NoWait";
-        IndexedDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName(cacheName);
-        cattr.setMaxKeySize(100);
-        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTest");
-        IndexedDiskCache<String, String> diskCache = new IndexedDiskCache<>(cattr);
-
-        // DO WORK
-        for (int i = 0; i <= items; i++)
-        {
-            diskCache.update(new CacheElement<>(cacheName, i + ":key", cacheName + " data " + i));
-        }
-
-        Map<String, ICacheElement<String, String>> matchingResults = diskCache.getMatching("1.8.+");
-
-        // VERIFY
-        assertEquals("Wrong number returned", 10, matchingResults.size());
-        // System.out.println( "matchingResults.keySet() " + matchingResults.keySet() );
-        // System.out.println( "\nAFTER TEST \n" + diskCache.getStats() );
-    }
-
-    /**
-     * Verify that the block disk cache can handle utf encoded strings.
-     * <p>
-     *
-     * @throws Exception
-     */
-    public void testUTF8String() throws Exception
-    {
-        String string = "IÒtÎrn‚tiÙn‡lizÊti¯n";
-        StringBuilder sb = new StringBuilder();
-        sb.append(string);
-        for (int i = 0; i < 4; i++)
-        {
-            sb.append(sb.toString()); // big string
-        }
-        string = sb.toString();
-
-        // System.out.println( "The string contains " + string.length() + " characters" );
-
-        String cacheName = "testUTF8String";
-
-        IndexedDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName(cacheName);
-        cattr.setMaxKeySize(100);
-        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTest");
-        IndexedDiskCache<String, String> diskCache = new IndexedDiskCache<>(cattr);
-
-        // DO WORK
-        diskCache.update(new CacheElement<>(cacheName, "x", string));
-
-        // VERIFY
-        assertNotNull(diskCache.get("x"));
-        Thread.sleep(1000);
-        ICacheElement<String, String> afterElement = diskCache.get("x");
-        assertNotNull(afterElement);
-        // System.out.println( "afterElement = " + afterElement );
-        String after = afterElement.getVal();
-
-        assertNotNull(after);
-        assertEquals("wrong string after retrieval", string, after);
-    }
-
-    /**
-     * Verify that the block disk cache can handle utf encoded strings.
-     * <p>
-     *
-     * @throws Exception
-     */
-    public void testUTF8ByteArray() throws Exception
-    {
-        String string = "IÒtÎrn‚tiÙn‡lizÊti¯n";
-        StringBuilder sb = new StringBuilder();
-        sb.append(string);
-        for (int i = 0; i < 4; i++)
-        {
-            sb.append(sb.toString()); // big string
-        }
-        string = sb.toString();
-        // System.out.println( "The string contains " + string.length() + " characters" );
-        byte[] bytes = string.getBytes(StandardCharsets.UTF_8);
-
-        String cacheName = "testUTF8ByteArray";
-
-        IndexedDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName(cacheName);
-        cattr.setMaxKeySize(100);
-        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTest");
-        IndexedDiskCache<String, byte[]> diskCache = new IndexedDiskCache<>(cattr);
-
-        // DO WORK
-        diskCache.update(new CacheElement<>(cacheName, "x", bytes));
-
-        // VERIFY
-        assertNotNull(diskCache.get("x"));
-        Thread.sleep(1000);
-        ICacheElement<String, byte[]> afterElement = diskCache.get("x");
-        assertNotNull(afterElement);
-        // System.out.println( "afterElement = " + afterElement );
-        byte[] after = afterElement.getVal();
-
-        assertNotNull(after);
-        assertEquals("wrong bytes after retrieval", string, new String(after, StandardCharsets.UTF_8));
-    }
-
-    /**
-     * Verify the item makes it to disk.
-     * <p>
-     *
-     * @throws IOException
-     */
-    public void testProcessUpdate_Simple() throws IOException
-    {
-        // SETUP
-        String cacheName = "testProcessUpdate_Simple";
-        IndexedDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName(cacheName);
-        cattr.setMaxKeySize(100);
-        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTest");
-        IndexedDiskCache<String, String> diskCache = new IndexedDiskCache<>(cattr);
-
-        String key = "myKey";
-        String value = "myValue";
-        ICacheElement<String, String> ce = new CacheElement<>(cacheName, key, value);
-
-        // DO WORK
-        diskCache.processUpdate(ce);
-        ICacheElement<String, String> result = diskCache.processGet(key);
-
-        // VERIFY
-        assertNotNull("Should have a result", result);
-        long fileSize = diskCache.getDataFileSize();
-        assertTrue("File should be greater than 0", fileSize > 0);
-    }
-
-    /**
-     * Verify the item makes it to disk.
-     * <p>
-     *
-     * @throws IOException
-     */
-    public void testProcessUpdate_SameKeySameSize() throws IOException
-    {
-        // SETUP
-        String cacheName = "testProcessUpdate_SameKeySameSize";
-        IndexedDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName(cacheName);
-        cattr.setMaxKeySize(100);
-        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTest");
-        IndexedDiskCache<String, String> diskCache = new IndexedDiskCache<>(cattr);
-
-        String key = "myKey";
-        String value = "myValue";
-        ICacheElement<String, String> ce1 = new CacheElement<>(cacheName, key, value);
-
-        // DO WORK
-        diskCache.processUpdate(ce1);
-        long fileSize1 = diskCache.getDataFileSize();
-
-        // DO WORK
-        ICacheElement<String, String> ce2 = new CacheElement<>(cacheName, key, value);
-        diskCache.processUpdate(ce2);
-        ICacheElement<String, String> result = diskCache.processGet(key);
-
-        // VERIFY
-        assertNotNull("Should have a result", result);
-        long fileSize2 = diskCache.getDataFileSize();
-        assertEquals("File should be the same", fileSize1, fileSize2);
-        int binSize = diskCache.getRecyleBinSize();
-        assertEquals("Should be nothing in the bin.", 0, binSize);
-    }
-
-    /**
-     * Verify the item makes it to disk.
-     * <p>
-     *
-     * @throws IOException
-     */
-    public void testProcessUpdate_SameKeySmallerSize() throws IOException
-    {
-        // SETUP
-        String cacheName = "testProcessUpdate_SameKeySmallerSize";
-        IndexedDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName(cacheName);
-        cattr.setMaxKeySize(100);
-        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTest");
-        IndexedDiskCache<String, String> diskCache = new IndexedDiskCache<>(cattr);
-
-        String key = "myKey";
-        String value = "myValue";
-        String value2 = "myValu";
-        ICacheElement<String, String> ce1 = new CacheElement<>(cacheName, key, value);
-
-        // DO WORK
-        diskCache.processUpdate(ce1);
-        long fileSize1 = diskCache.getDataFileSize();
-
-        // DO WORK
-        ICacheElement<String, String> ce2 = new CacheElement<>(cacheName, key, value2);
-        diskCache.processUpdate(ce2);
-        ICacheElement<String, String> result = diskCache.processGet(key);
-
-        // VERIFY
-        assertNotNull("Should have a result", result);
-        long fileSize2 = diskCache.getDataFileSize();
-        assertEquals("File should be the same", fileSize1, fileSize2);
-        int binSize = diskCache.getRecyleBinSize();
-        assertEquals("Should be nothing in the bin.", 0, binSize);
-    }
-
-    /**
-     * Verify that the old slot gets in the recycle bin.
-     * <p>
-     *
-     * @throws IOException
-     */
-    public void testProcessUpdate_SameKeyBiggerSize() throws IOException
-    {
-        // SETUP
-        String cacheName = "testProcessUpdate_SameKeyBiggerSize";
-        IndexedDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName(cacheName);
-        cattr.setMaxKeySize(100);
-        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTest");
-        IndexedDiskCache<String, String> diskCache = new IndexedDiskCache<>(cattr);
-
-        String key = "myKey";
-        String value = "myValue";
-        String value2 = "myValue2";
-        ICacheElement<String, String> ce1 = new CacheElement<>(cacheName, key, value);
-
-        // DO WORK
-        diskCache.processUpdate(ce1);
-        long fileSize1 = diskCache.getDataFileSize();
-
-        // DO WORK
-        ICacheElement<String, String> ce2 = new CacheElement<>(cacheName, key, value2);
-        diskCache.processUpdate(ce2);
-        ICacheElement<String, String> result = diskCache.processGet(key);
-
-        // VERIFY
-        assertNotNull("Should have a result", result);
-        long fileSize2 = diskCache.getDataFileSize();
-        assertTrue("File should be greater.", fileSize1 < fileSize2);
-        int binSize = diskCache.getRecyleBinSize();
-        assertEquals("Should be one in the bin.", 1, binSize);
-    }
-
-    public void testLoadFromDisk() throws Exception
-    {
-        for (int i = 0; i < 15; i++)
-        { // usually after 2 time it fails
-            oneLoadFromDisk();
-        }
-    }
-
-    public void oneLoadFromDisk() throws Exception
-    {
-        // initialize object to be stored
-        String string = "IÒtÎrn‚tiÙn‡lizÊti¯n";
-        StringBuilder sb = new StringBuilder();
-        sb.append(string);
-        for (int i = 0; i < 4; i++)
-        {
-            sb.append(sb.toString()); // big string
-        }
-        string = sb.toString();
-
-        // initialize cache
-        String cacheName = "testLoadFromDisk";
-        IndexedDiskCacheAttributes cattr = getCacheAttributes();
-        cattr.setCacheName(cacheName);
-        cattr.setMaxKeySize(100);
-        cattr.setDiskPath("target/test-sandbox/BlockDiskCacheUnitTest");
-        IndexedDiskCache<String, String> diskCache = new IndexedDiskCache<>(cattr);
-
-        // DO WORK
-        for (int i = 0; i < 50; i++)
-        {
-            diskCache.update(new CacheElement<>(cacheName, "x" + i, string));
-        }
-        // Thread.sleep(1000);
-        // VERIFY
-        diskCache.dispose();
-        // Thread.sleep(1000);
-
-        diskCache = new IndexedDiskCache<>(cattr);
-
-        for (int i = 0; i < 50; i++)
-        {
-            ICacheElement<String, String> afterElement = diskCache.get("x" + i);
-            assertNotNull("Missing element from cache. Cache size: " + diskCache.getSize() + " element: x" + i, afterElement);
-            assertEquals("wrong string after retrieval", string, afterElement.getVal());
-        }
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheConcurrentNoDeadLockUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheConcurrentNoDeadLockUnitTest.java
deleted file mode 100644
index eca55d6..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheConcurrentNoDeadLockUnitTest.java
+++ /dev/null
@@ -1,148 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.indexed;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.extensions.ActiveTestSuite;
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.textui.TestRunner;
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.engine.control.CompositeCacheManager;
-
-/**
- * Test which exercises the indexed disk cache. Runs three threads against the
- * same region.
- *
- * @version $Id: TestDiskCacheConcurrentForDeadLock.java,v 1.2 2005/02/01
- *          00:01:59 asmuts Exp $
- */
-public class IndexedDiskCacheConcurrentNoDeadLockUnitTest
-    extends TestCase
-{
-    /**
-     * Constructor for the TestDiskCache object.
-     *
-     * @param testName
-     */
-    public IndexedDiskCacheConcurrentNoDeadLockUnitTest( String testName )
-    {
-        super( testName );
-    }
-
-    /**
-     * Main method passes this test to the text test runner.
-     *
-     * @param args
-     */
-    public static void main( String args[] )
-    {
-        String[] testCaseName = { IndexedDiskCacheConcurrentNoDeadLockUnitTest.class.getName() };
-        TestRunner.main( testCaseName );
-    }
-
-    /**
-     * A unit test suite for JUnit
-     *
-     * @return The test suite
-     */
-    public static Test suite()
-    {
-        ActiveTestSuite suite = new ActiveTestSuite();
-
-        suite.addTest( new IndexedDiskCacheRandomConcurrentTestUtil( "testIndexedDiskCache1" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "indexedRegion4", 1, 200, 1 );
-            }
-        } );
-
-        suite.addTest( new IndexedDiskCacheRandomConcurrentTestUtil( "testIndexedDiskCache2" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "indexedRegion4", 10000, 50000, 2 );
-            }
-        } );
-
-        suite.addTest( new IndexedDiskCacheRandomConcurrentTestUtil( "testIndexedDiskCache3" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "indexedRegion4", 10000, 50000, 3 );
-            }
-        } );
-
-        suite.addTest( new IndexedDiskCacheRandomConcurrentTestUtil( "testIndexedDiskCache4" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "indexedRegion4", 10000, 50000, 4 );
-            }
-        } );
-
-        suite.addTest( new IndexedDiskCacheRandomConcurrentTestUtil( "testIndexedDiskCache5" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "indexedRegion4", 10000, 50000, 5 );
-            }
-        } );
-
-        return suite;
-    }
-
-    /**
-     * Test setup
-     */
-    @Override
-    public void setUp()
-    {
-        JCS.setConfigFilename( "/TestDiskCacheCon.ccf" );
-    }
-
-    /**
-     * Test tearDown. Dispose of the cache.
-     */
-    @Override
-    public void tearDown()
-    {
-        try
-        {
-            CompositeCacheManager cacheMgr = CompositeCacheManager.getInstance();
-            cacheMgr.shutDown();
-        }
-        catch ( Exception e )
-        {
-            // log.error(e);
-        }
-    }
-
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheConcurrentUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheConcurrentUnitTest.java
deleted file mode 100644
index 0661254..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheConcurrentUnitTest.java
+++ /dev/null
@@ -1,251 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.indexed;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.extensions.ActiveTestSuite;
-import junit.framework.Test;
-import junit.framework.TestCase;
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Test which exercises the indexed disk cache. This one uses three different
- * regions for thre threads.
- *
- * @version $Id$
- */
-public class IndexedDiskCacheConcurrentUnitTest
-    extends TestCase
-{
-    /**
-     * Number of items to cache, twice the configured maxObjects for the memory
-     * cache regions.
-     */
-    private static int items = 200;
-
-    /**
-     * Constructor for the TestDiskCache object.
-     *
-     * @param testName
-     */
-    public IndexedDiskCacheConcurrentUnitTest( String testName )
-    {
-        super( testName );
-    }
-
-    /**
-     * Main method passes this test to the text test runner.
-     *
-     * @param args
-     */
-    public static void main( String args[] )
-    {
-        String[] testCaseName = { IndexedDiskCacheConcurrentUnitTest.class.getName() };
-        junit.textui.TestRunner.main( testCaseName );
-    }
-
-    /**
-     * A unit test suite for JUnit
-     *
-     * @return The test suite
-     */
-    public static Test suite()
-    {
-        ActiveTestSuite suite = new ActiveTestSuite();
-
-        suite.addTest( new IndexedDiskCacheConcurrentUnitTest( "testIndexedDiskCache1" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "indexedRegion1" );
-            }
-        } );
-
-        suite.addTest( new IndexedDiskCacheConcurrentUnitTest( "testIndexedDiskCache2" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "indexedRegion2" );
-            }
-        } );
-
-        suite.addTest( new IndexedDiskCacheConcurrentUnitTest( "testIndexedDiskCache3" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "indexedRegion3" );
-            }
-        } );
-
-        suite.addTest( new IndexedDiskCacheConcurrentUnitTest( "testIndexedDiskCache4" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegionInRange( "indexedRegion3", 300, 600 );
-            }
-        } );
-
-        return suite;
-    }
-
-    /**
-     * Test setup
-     */
-    @Override
-    public void setUp()
-    {
-        JCS.setConfigFilename( "/TestDiskCache.ccf" );
-    }
-
-    /**
-     * Adds items to cache, gets them, and removes them. The item count is more
-     * than the size of the memory cache, so items should spool to disk.
-     *
-     * @param region
-     *            Name of the region to access
-     *
-     * @throws Exception
-     *                If an error occurs
-     */
-    public void runTestForRegion( String region )
-        throws Exception
-    {
-        CacheAccess<String, String> jcs = JCS.getInstance( region );
-
-        // Add items to cache
-        for ( int i = 0; i <= items; i++ )
-        {
-            jcs.put( i + ":key", region + " data " + i );
-        }
-
-        // Test that all items are in cache
-        for ( int i = 0; i <= items; i++ )
-        {
-            String value = jcs.get( i + ":key" );
-
-            assertEquals( region + " data " + i, value );
-        }
-
-        // Test that getElements returns all the expected values
-        Set<String> keys = new HashSet<>();
-        for ( int i = 0; i <= items; i++ )
-        {
-            keys.add( i + ":key" );
-        }
-
-        Map<String, ICacheElement<String, String>> elements = jcs.getCacheElements( keys );
-        for ( int i = 0; i <= items; i++ )
-        {
-            ICacheElement<String, String> element = elements.get( i + ":key" );
-            assertNotNull( "element " + i + ":key is missing", element );
-            assertEquals( "value " + i + ":key", region + " data " + i, element.getVal() );
-        }
-
-        // Remove all the items
-        for ( int i = 0; i <= items; i++ )
-        {
-            jcs.remove( i + ":key" );
-        }
-
-        // Verify removal
-        // another thread may have inserted since
-        for ( int i = 0; i <= items; i++ )
-        {
-            assertNull( "Removed key should be null: " + i + ":key" + "\n stats " + jcs.getStats(), jcs
-                .get( i + ":key" ) );
-        }
-    }
-
-    /**
-     * Adds items to cache, gets them, and removes them. The item count is more
-     * than the size of the memory cache, so items should spool to disk.
-     *
-     * @param region
-     *            Name of the region to access
-     * @param start
-     * @param end
-     *
-     * @throws Exception
-     *                If an error occurs
-     */
-    public void runTestForRegionInRange( String region, int start, int end )
-        throws Exception
-    {
-        CacheAccess<String, String> jcs = JCS.getInstance( region );
-
-        // Add items to cache
-        for ( int i = start; i <= end; i++ )
-        {
-            jcs.put( i + ":key", region + " data " + i );
-        }
-
-        // Test that all items are in cache
-        for ( int i = start; i <= end; i++ )
-        {
-            String value = jcs.get( i + ":key" );
-
-            assertEquals( region + " data " + i, value );
-        }
-
-        // Test that getElements returns all the expected values
-        Set<String> keys = new HashSet<>();
-        for ( int i = start; i <= end; i++ )
-        {
-            keys.add( i + ":key" );
-        }
-
-        Map<String, ICacheElement<String, String>> elements = jcs.getCacheElements( keys );
-        for ( int i = start; i <= end; i++ )
-        {
-            ICacheElement<String, String> element = elements.get( i + ":key" );
-            assertNotNull( "element " + i + ":key is missing", element );
-            assertEquals( "value " + i + ":key", region + " data " + i, element.getVal() );
-        }
-
-        // Remove all the items
-        for ( int i = start; i <= end; i++ )
-        {
-            jcs.remove( i + ":key" );
-        }
-
-//        System.out.println( jcs.getStats() );
-
-        // Verify removal
-        // another thread may have inserted since
-        for ( int i = start; i <= end; i++ )
-        {
-            assertNull( "Removed key should be null: " + i + ":key " + "\n stats " + jcs.getStats(), jcs.get( i
-                + ":key" ) );
-        }
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheDefragPerformanceTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheDefragPerformanceTest.java
deleted file mode 100644
index 7ce6788..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheDefragPerformanceTest.java
+++ /dev/null
@@ -1,180 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.indexed;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-
-import java.io.Serializable;
-import java.text.DecimalFormat;
-import java.util.Random;
-
-/**
- * This is for manually testing the defrag process.
- */
-public class IndexedDiskCacheDefragPerformanceTest
-    extends TestCase
-{
-    /** For readability */
-    private static final String LOG_DIVIDER = "---------------------------";
-
-    /** total to test with */
-    private static final int TOTAL_ELEMENTS = 30000;
-
-    /** time to wait */
-    private static final long SLEEP_TIME_DISK = 8000;
-
-    /** how often to log */
-    private static final int LOG_INCREMENT = 5000;
-
-    /** for getting memory usage */
-    private static Runtime rt = Runtime.getRuntime();
-
-    /** for displaying memory usage */
-    private static DecimalFormat format = new DecimalFormat( "#,###" );
-
-    /**
-     * @throws Exception
-     */
-    public void testRealTimeOptimization()
-        throws Exception
-    {
-        System.out.println( LOG_DIVIDER );
-        System.out.println( "JCS DEFRAG PERFORMANCE TESTS" );
-        System.out.println( LOG_DIVIDER );
-        logMemoryUsage();
-        IndexedDiskCacheDefragPerformanceTest.runRealTimeOptimizationTest();
-        logMemoryUsage();
-
-        System.out.println( LOG_DIVIDER );
-    }
-
-    /**
-     * @throws Exception
-     */
-    private static void runRealTimeOptimizationTest()
-        throws Exception
-    {
-        JCS.setConfigFilename( "/TestDiskCacheDefragPerformance.ccf" );
-        CacheAccess<Integer, Tile> jcs = JCS.getInstance( "defrag" );
-
-        Tile tile;
-        System.out.println( "Cache Defrag Test" );
-
-        Random random = new Random( 89 );
-        for ( int i = 0; i < TOTAL_ELEMENTS; i++ )
-        {
-            int bytes = random.nextInt( 20 );
-            // 4-24 KB
-            tile = new Tile( Integer.valueOf( i ), new byte[( bytes + 4 ) * 1024] );
-            // images
-
-            jcs.put( tile.id, tile );
-
-            if ( ( i != 0 ) && ( 0 == ( i % 100 ) ) )
-            {
-                jcs.get( Integer.valueOf( random.nextInt( i ) ) );
-            }
-
-            if ( 0 == ( i % LOG_INCREMENT ) )
-            {
-                System.out.print( i + ", " );
-                Thread.sleep( SLEEP_TIME_DISK );
-            }
-        }
-
-        System.out.println( LOG_DIVIDER );
-        System.out.println( "Total elements = " + TOTAL_ELEMENTS );
-        System.out.println( "Stats prior to sleeping " + jcs.getStats() );
-
-        // Allow system to settle down
-        System.out.println( "Sleeping for a a minute." );
-        Thread.sleep( 60000 );
-
-        System.out.println( LOG_DIVIDER );
-        System.out.println( "Stats prior to dispose " + jcs.getStats() );
-
-        jcs.dispose();
-        System.out.println( LOG_DIVIDER );
-        System.out.println( "Stats after dispose " + jcs.getStats() );
-        System.out.println( "Done testing." );
-    }
-
-    /**
-     * Logs the memory usage.
-     */
-    private static void logMemoryUsage()
-    {
-        long byte2MB = 1024 * 1024;
-        long total = rt.totalMemory() / byte2MB;
-        long free = rt.freeMemory() / byte2MB;
-        long used = total - free;
-        System.out.println( LOG_DIVIDER );
-        System.out.println( "Memory:" + " Used:" + format.format( used ) + "MB" + " Free:" + format.format( free )
-            + "MB" + " Total:" + format.format( total ) + "MB" );
-    }
-
-    /**
-     * Resembles a cached image.
-     */
-    private static class Tile
-        implements Serializable
-    {
-        /** Don't change */
-        private static final long serialVersionUID = 1L;
-
-        /**
-         * Key
-         */
-        public Integer id;
-
-        /**Byte size
-         *
-         */
-        public byte[] imageBytes;
-
-        /**
-         * @param id
-         * @param imageBytes
-         */
-        public Tile( Integer id, byte[] imageBytes )
-        {
-            this.id = id;
-            this.imageBytes = imageBytes;
-        }
-    }
-
-    /**
-     * @param args
-     */
-    public static void main( String args[] )
-    {
-        try
-        {
-            IndexedDiskCacheDefragPerformanceTest tester = new IndexedDiskCacheDefragPerformanceTest();
-            tester.testRealTimeOptimization();
-        }
-        catch ( Exception e )
-        {
-            e.printStackTrace();
-        }
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheKeyStoreUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheKeyStoreUnitTest.java
deleted file mode 100644
index 239b575..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheKeyStoreUnitTest.java
+++ /dev/null
@@ -1,149 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.indexed;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.ElementAttributes;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-
-/**
- * Test store and load keys.
- *
- * @author Aaron Smuts
- *
- */
-public class IndexedDiskCacheKeyStoreUnitTest
-    extends TestCase
-{
-
-    /**
-     * Add some keys, store them, load them from disk, then check to see that we
-     * can get the items.
-     *
-     * @throws Exception
-     *
-     */
-    public void testStoreKeys()
-        throws Exception
-    {
-        IndexedDiskCacheAttributes cattr = new IndexedDiskCacheAttributes();
-        cattr.setCacheName( "testStoreKeys" );
-        cattr.setMaxKeySize( 100 );
-        cattr.setDiskPath( "target/test-sandbox/KeyStoreUnitTest" );
-        IndexedDiskCache<String, String> disk = new IndexedDiskCache<>( cattr );
-
-        disk.processRemoveAll();
-
-        int cnt = 25;
-        for ( int i = 0; i < cnt; i++ )
-        {
-            IElementAttributes eAttr = new ElementAttributes();
-            eAttr.setIsSpool( true );
-            ICacheElement<String, String> element = new CacheElement<>( cattr.getCacheName(), "key:" + i, "data:" + i );
-            element.setElementAttributes( eAttr );
-            disk.processUpdate( element );
-        }
-
-        for ( int i = 0; i < cnt; i++ )
-        {
-            ICacheElement<String, String> element = disk.processGet( "key:" + i );
-            assertNotNull( "presave, Should have received an element.", element );
-            assertEquals( "presave, element is wrong.", "data:" + i, element.getVal() );
-        }
-
-        disk.saveKeys();
-
-        disk.loadKeys();
-
-        assertEquals( "The disk is the wrong size.", cnt, disk.getSize() );
-
-        for ( int i = 0; i < cnt; i++ )
-        {
-            ICacheElement<String, String> element = disk.processGet( "key:" + i );
-            assertNotNull( "postsave, Should have received an element.", element );
-            assertEquals( "postsave, element is wrong.", "data:" + i, element.getVal() );
-        }
-
-        disk.dump();
-
-    }
-
-
-    /**
-     * Add some elements, remove 1, call optimize, verify that the removed isn't present.
-     *
-     * We should also compare the data file sizes. . . .
-     *
-     * @throws Exception
-     *
-     */
-    public void testOptiimize()
-        throws Exception
-    {
-        IndexedDiskCacheAttributes cattr = new IndexedDiskCacheAttributes();
-        cattr.setCacheName( "testOptimize" );
-        cattr.setMaxKeySize( 100 );
-        cattr.setDiskPath( "target/test-sandbox/KeyStoreUnitTest" );
-        IndexedDiskCache<String, String> disk = new IndexedDiskCache<>( cattr );
-
-        disk.processRemoveAll();
-
-        int cnt = 25;
-        for ( int i = 0; i < cnt; i++ )
-        {
-            IElementAttributes eAttr = new ElementAttributes();
-            eAttr.setIsSpool( true );
-            ICacheElement<String, String> element = new CacheElement<>( cattr.getCacheName(), "key:" + i, "data:" + i );
-            element.setElementAttributes( eAttr );
-            disk.processUpdate( element );
-        }
-
-        long preAddRemoveSize = disk.getDataFileSize();
-
-        IElementAttributes eAttr = new ElementAttributes();
-        eAttr.setIsSpool( true );
-        ICacheElement<String, String> elementSetup = new CacheElement<>( cattr.getCacheName(), "key:" + "A", "data:" + "A" );
-        elementSetup.setElementAttributes( eAttr );
-        disk.processUpdate( elementSetup );
-
-        ICacheElement<String, String> elementRet = disk.processGet( "key:" + "A" );
-        assertNotNull( "postsave, Should have received an element.", elementRet );
-        assertEquals( "postsave, element is wrong.", "data:" + "A", elementRet.getVal() );
-
-        disk.remove( "key:" + "A" );
-
-        long preSize = disk.getDataFileSize();
-        // synchronous versoin
-        disk.optimizeFile(); //deoptimizeRealTime();
-        long postSize = disk.getDataFileSize();
-
-        assertTrue( "Should be smaller. postsize="+postSize+" preSize="+preSize, postSize < preSize );
-        assertEquals( "Should be the same size after optimization as before add and remove.", preAddRemoveSize, postSize );
-
-        for ( int i = 0; i < cnt; i++ )
-        {
-            ICacheElement<String, String> element = disk.processGet( "key:" + i );
-            assertNotNull( "postsave, Should have received an element.", element );
-            assertEquals( "postsave, element is wrong.", "data:" + i, element.getVal() );
-        }
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheNoMemoryUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheNoMemoryUnitTest.java
deleted file mode 100644
index bbce378..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheNoMemoryUnitTest.java
+++ /dev/null
@@ -1,178 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.indexed;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.extensions.ActiveTestSuite;
-import junit.framework.Test;
-import junit.framework.TestCase;
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Test which exercises the indexed disk cache. This one uses three different
- * regions for thre threads. It uses a config file that specifies 0 items in
- * memory.
- */
-public class IndexedDiskCacheNoMemoryUnitTest
-    extends TestCase
-{
-    /**
-     * Number of items to cache; the configured maxObjects for the memory cache
-     * regions is 0.
-     */
-    private static int items = 2000;
-
-    /**
-     * @param testName
-     */
-    public IndexedDiskCacheNoMemoryUnitTest( String testName )
-    {
-        super( testName );
-    }
-
-    /**
-     * Main method passes this test to the text test runner.
-     * <p>
-     * @param args
-     */
-    public static void main( String args[] )
-    {
-        String[] testCaseName = { IndexedDiskCacheNoMemoryUnitTest.class.getName() };
-        junit.textui.TestRunner.main( testCaseName );
-    }
-
-    /**
-     * A unit test suite for JUnit
-     * <p>
-     * @return The test suite
-     */
-    public static Test suite()
-    {
-        ActiveTestSuite suite = new ActiveTestSuite();
-
-        suite.addTest( new IndexedDiskCacheNoMemoryUnitTest( "testIndexedDiskCache1" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "indexedRegion1" );
-            }
-        } );
-
-        suite.addTest( new IndexedDiskCacheNoMemoryUnitTest( "testIndexedDiskCache2" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "indexedRegion2" );
-            }
-        } );
-
-        suite.addTest( new IndexedDiskCacheNoMemoryUnitTest( "testIndexedDiskCache3" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "indexedRegion3" );
-            }
-        } );
-
-        return suite;
-    }
-
-    /**
-     * Test setup
-     */
-    @Override
-    public void setUp()
-    {
-        JCS.setConfigFilename( "/TestDiskCacheNoMemory.ccf" );
-    }
-
-    /**
-     * Adds items to cache, gets them, and removes them. The item count is more
-     * than the size of the memory cache, so items should spool to disk.
-     *
-     * @param region
-     *            Name of the region to access
-     *
-     * @throws Exception
-     *                If an error occurs
-     */
-    public void runTestForRegion( String region )
-        throws Exception
-    {
-        CacheAccess<String, String> jcs = JCS.getInstance( region );
-
-        // Add items to cache
-
-        for ( int i = 0; i <= items; i++ )
-        {
-            jcs.put( i + ":key", region + " data " + i );
-        }
-
-        // Test that all items are in cache
-
-        for ( int i = 0; i <= items; i++ )
-        {
-            String value = jcs.get( i + ":key" );
-
-            assertEquals( region + " data " + i, value );
-        }
-
-        // Test that getElements returns all the expected values
-        Set<String> keys = new HashSet<>();
-        for ( int i = 0; i <= items; i++ )
-        {
-            keys.add( i + ":key" );
-        }
-
-        Map<String, ICacheElement<String, String>> elements = jcs.getCacheElements( keys );
-        for ( int i = 0; i <= items; i++ )
-        {
-            ICacheElement<String, String> element = elements.get( i + ":key" );
-            assertNotNull( "element " + i + ":key is missing", element );
-            assertEquals( "value " + i + ":key", region + " data " + i, element.getVal() );
-        }
-
-        // Remove all the items
-        for ( int i = 0; i <= items; i++ )
-        {
-            jcs.remove( i + ":key" );
-        }
-
-        // Verify removal
-        for ( int i = 0; i <= items; i++ )
-        {
-            assertNull( "Removed key should be null: " + i + ":key" + "\n stats " + jcs.getStats(), jcs.get( i + ":key" ) );
-        }
-
-        // dump the stats to the report
-//        System.out.println( jcs.getStats() );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheOptimizationUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheOptimizationUnitTest.java
deleted file mode 100644
index a76740d..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheOptimizationUnitTest.java
+++ /dev/null
@@ -1,95 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.indexed;
-
-import org.apache.commons.jcs.auxiliary.disk.DiskTestObject;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.utils.timing.SleepUtil;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-
-/**
- * Tests for the optimization routine.
- * <p>
- * @author Aaron Smuts
- */
-public class IndexedDiskCacheOptimizationUnitTest
-    extends TestCase
-{
-    /**
-     * Set the optimize at remove count to 10. Add 20. Check the file size. Remove 10. Check the
-     * times optimized. Check the file size.
-     * @throws Exception
-     */
-    public void testBasicOptimization()
-        throws Exception
-    {
-        // SETUP
-        int removeCount = 50;
-
-        IndexedDiskCacheAttributes cattr = new IndexedDiskCacheAttributes();
-        cattr.setCacheName( "testOptimization" );
-        cattr.setMaxKeySize( removeCount * 2 );
-        cattr.setOptimizeAtRemoveCount( removeCount );
-        cattr.setDiskPath( "target/test-sandbox/testOptimization" );
-        IndexedDiskCache<Integer, DiskTestObject> disk = new IndexedDiskCache<>( cattr );
-
-        disk.removeAll();
-
-        int numberToInsert = removeCount * 3;
-        ICacheElement<Integer, DiskTestObject>[] elements = DiskTestObjectUtil
-            .createCacheElementsWithTestObjectsOfVariableSizes( numberToInsert, cattr.getCacheName() );
-
-        for ( int i = 0; i < elements.length; i++ )
-        {
-            disk.processUpdate( elements[i] );
-        }
-
-
-        Thread.sleep( 1000 );
-        long sizeBeforeRemove = disk.getDataFileSize();
-        // System.out.println( "file sizeBeforeRemove " + sizeBeforeRemove );
-        // System.out.println( "totalSize inserted " + DiskTestObjectUtil.totalSize( elements, numberToInsert ) );
-
-        // DO WORK
-        for ( int i = 0; i < removeCount; i++ )
-        {
-            disk.processRemove( Integer.valueOf( i ) );
-        }
-
-        SleepUtil.sleepAtLeast( 1000 );
-
-        disk.optimizeFile();
-        // VERIFY
-        long sizeAfterRemove = disk.getDataFileSize();
-        long expectedSizeAfterRemove = DiskTestObjectUtil.totalSize( elements, removeCount, elements.length );
-
-        // test is prone to failure for timing reasons.
-        if ( expectedSizeAfterRemove != sizeAfterRemove )
-        {
-            SleepUtil.sleepAtLeast( 2000 );
-        }
-
-        assertTrue( "The post optimization size should be smaller."
-                +"sizeAfterRemove=" + sizeAfterRemove + " sizeBeforeRemove= " +sizeBeforeRemove
-                , sizeAfterRemove < sizeBeforeRemove );
-        assertEquals( "The file size is not as expected size.", expectedSizeAfterRemove, sizeAfterRemove );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheRandomConcurrentTestUtil.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheRandomConcurrentTestUtil.java
deleted file mode 100644
index 5d7b8cb..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheRandomConcurrentTestUtil.java
+++ /dev/null
@@ -1,86 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.indexed;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.access.TestCacheAccess;
-
-/**
- * This is used by other tests to generate a random load on the disk cache.
- */
-public class IndexedDiskCacheRandomConcurrentTestUtil
-    extends TestCase
-{
-
-    /**
-     * Constructor for the TestDiskCache object.
-     *
-     * @param testName
-     */
-    public IndexedDiskCacheRandomConcurrentTestUtil( String testName )
-    {
-        super( testName );
-    }
-
-    /**
-     * Randomly adds items to cache, gets them, and removes them. The range
-     * count is more than the size of the memory cache, so items should spool to
-     * disk.
-     *
-     * @param region
-     *            Name of the region to access
-     * @param range
-     * @param numOps
-     * @param testNum
-     *
-     * @throws Exception
-     *                If an error occurs
-     */
-    public void runTestForRegion( String region, int range, int numOps, int testNum )
-        throws Exception
-    {
-        // run a rondom operation test to detect deadlocks
-        TestCacheAccess tca = new TestCacheAccess( "/TestDiskCacheCon.ccf" );
-        tca.setRegion( region );
-        tca.random( range, numOps );
-
-        // make sure a simple put then get works
-        // this may fail if the other tests are flooding the disk cache
-        CacheAccess<String, String> jcs = JCS.getInstance( region );
-        String key = "testKey" + testNum;
-        String data = "testData" + testNum;
-        jcs.put( key, data );
-        String value = jcs.get( key );
-        assertEquals( data, value );
-
-    }
-
-    /**
-     * Test setup
-     */
-    @Override
-    public void setUp()
-    {
-        JCS.setConfigFilename( "/TestDiskCacheCon.ccf" );
-    }
-
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheSameRegionConcurrentUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheSameRegionConcurrentUnitTest.java
deleted file mode 100644
index 244410c..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheSameRegionConcurrentUnitTest.java
+++ /dev/null
@@ -1,208 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.indexed;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.extensions.ActiveTestSuite;
-import junit.framework.Test;
-import junit.framework.TestCase;
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Test which exercises the indexed disk cache. Runs three threads against the
- * same region.
- */
-public class IndexedDiskCacheSameRegionConcurrentUnitTest
-    extends TestCase
-{
-    /**
-     * Constructor for the TestDiskCache object.
-     *
-     * @param testName
-     */
-    public IndexedDiskCacheSameRegionConcurrentUnitTest( String testName )
-    {
-        super( testName );
-    }
-
-    /**
-     * Main method passes this test to the text test runner.
-     *
-     * @param args
-     */
-    public static void main( String args[] )
-    {
-        String[] testCaseName = { IndexedDiskCacheSameRegionConcurrentUnitTest.class.getName() };
-        junit.textui.TestRunner.main( testCaseName );
-    }
-
-    /**
-     * A unit test suite for JUnit
-     *
-     * @return The test suite
-     */
-    public static Test suite()
-    {
-        ActiveTestSuite suite = new ActiveTestSuite();
-
-        suite.addTest( new IndexedDiskCacheSameRegionConcurrentUnitTest( "testIndexedDiskCache1" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "indexedRegion4", 0, 200 );
-            }
-        } );
-
-        suite.addTest( new IndexedDiskCacheSameRegionConcurrentUnitTest( "testIndexedDiskCache2" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "indexedRegion4", 1000, 1200 );
-            }
-        } );
-
-        suite.addTest( new IndexedDiskCacheSameRegionConcurrentUnitTest( "testIndexedDiskCache3" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "indexedRegion4", 2000, 2200 );
-            }
-        } );
-
-        suite.addTest( new IndexedDiskCacheSameRegionConcurrentUnitTest( "testIndexedDiskCache4" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "indexedRegion4", 2200, 5200 );
-            }
-        } );
-
-        suite.addTest( new IndexedDiskCacheSameRegionConcurrentUnitTest( "testIndexedDiskCache5" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "indexedRegion4", 0, 5100 );
-            }
-        } );
-
-        return suite;
-    }
-
-    /**
-     * Test setup
-     */
-    @Override
-    public void setUp()
-    {
-        JCS.setConfigFilename( "/TestDiskCacheCon.ccf" );
-    }
-
-    // /**
-    // * Tests the region which uses the indexed disk cache
-    // */
-    // public void testIndexedDiskCache()
-    // throws Exception
-    // {
-    // runTestForRegion( "indexedRegion" );
-    // }
-    //
-    // /**
-    // * Tests the region which uses the indexed disk cache
-    // */
-    // public void testIndexedDiskCache2()
-    // throws Exception
-    // {
-    // runTestForRegion( "indexedRegion2" );
-    // }
-
-    /**
-     * Adds items to cache, gets them, and removes them. The item count is more
-     * than the size of the memory cache, so items should spool to disk.
-     *
-     * @param region
-     *            Name of the region to access
-     * @param start
-     * @param end
-     *
-     * @throws Exception
-     *                If an error occurs
-     */
-    public void runTestForRegion( String region, int start, int end )
-        throws Exception
-    {
-        CacheAccess<String, String> jcs = JCS.getInstance( region );
-
-        // Add items to cache
-
-        for ( int i = start; i <= end; i++ )
-        {
-            jcs.put( i + ":key", region + " data " + i );
-        }
-
-        // Test that all items are in cache
-
-        for ( int i = start; i <= end; i++ )
-        {
-            String key = i + ":key";
-            String value = jcs.get( key );
-
-            assertEquals( "Wrong value for key [" + key + "]", region + " data " + i, value );
-        }
-
-        // Test that getElements returns all the expected values
-        Set<String> keys = new HashSet<>();
-        for ( int i = start; i <= end; i++ )
-        {
-            keys.add( i + ":key" );
-        }
-
-        Map<String, ICacheElement<String, String>> elements = jcs.getCacheElements( keys );
-        for ( int i = start; i <= end; i++ )
-        {
-            ICacheElement<String, String> element = elements.get( i + ":key" );
-            assertNotNull( "element " + i + ":key is missing", element );
-            assertEquals( "value " + i + ":key", region + " data " + i, element.getVal() );
-        }
-
-        // you can't remove in one thread and expect them to be in another //
-        //          Remove all the items
-        //
-        //          for ( int i = start; i <= end; i++ ) { jcs.remove( i + ":key" ); } //
-        //          Verify removal
-        //
-        //          for ( int i = start; i <= end; i++ ) { assertNull( "Removed key
-        //          should be null: " + i + ":key", jcs.get( i + ":key" ) ); }
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheSteadyLoadTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheSteadyLoadTest.java
deleted file mode 100644
index ebb24d3..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/IndexedDiskCacheSteadyLoadTest.java
+++ /dev/null
@@ -1,153 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.indexed;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.auxiliary.disk.DiskTestObject;
-import org.apache.commons.jcs.utils.timing.ElapsedTimer;
-
-import java.text.DecimalFormat;
-import java.util.Random;
-
-/**
- * This allows you to put thousands of large objects into the disk cache and to force removes to
- * trigger optimizations along the way.
- * <p>
- * @author Aaron Smuts
- */
-public class IndexedDiskCacheSteadyLoadTest
-    extends TestCase
-{
-    /** For display */
-    private static final String LOG_DIVIDER = "---------------------------";
-
-    /** For getting memory info */
-    private static Runtime rt = Runtime.getRuntime();
-
-    /** For display */
-    private static DecimalFormat format = new DecimalFormat( "#,###" );
-
-    /**
-     * Insert 2000 wait 1 second, repeat. Average 1000 / sec.
-     * <p>
-     * @throws Exception
-     */
-    public void testRunSteadyLoadTest()
-        throws Exception
-    {
-        JCS.setConfigFilename( "/TestDiskCacheSteadyLoad.ccf" );
-
-        System.out.println( "runSteadyLoadTest" );
-
-        logMemoryUsage();
-
-        int numPerRun = 200;
-        long pauseBetweenRuns = 1000;
-        int runCount = 0;
-        int runs = 1000;
-        int upperKB = 50;
-
-        CacheAccess<String, DiskTestObject> jcs = JCS.getInstance( ( numPerRun / 2 ) + "aSecond" );
-
-        ElapsedTimer timer = new ElapsedTimer();
-        int numToGet = numPerRun * ( runs / 10 );
-        for ( int i = 0; i < numToGet; i++ )
-        {
-            jcs.get( String.valueOf( i ) );
-        }
-        System.out.println( LOG_DIVIDER );
-        System.out.println( "After getting " + numToGet );
-        System.out.println( "Elapsed " + timer.getElapsedTimeString() );
-        logMemoryUsage();
-
-        jcs.clear();
-        Thread.sleep( 3000 );
-        System.out.println( LOG_DIVIDER );
-        System.out.println( "Start putting" );
-
-        long totalSize = 0;
-        int totalPut = 0;
-
-        Random random = new Random( 89 );
-        while ( runCount < runs )
-        {
-            runCount++;
-            for ( int i = 0; i < numPerRun; i++ )
-            {
-                // 1/2 upper to upperKB-4 KB
-                int kiloBytes = Math.max( upperKB / 2, random.nextInt( upperKB ) );
-                int bytes = ( kiloBytes ) * 1024;
-                totalSize += bytes;
-                totalPut++;
-                DiskTestObject object = new DiskTestObject( Integer.valueOf( i ), new byte[bytes] );
-                jcs.put( String.valueOf( totalPut ), object );
-            }
-
-            // remove half of those inserted the previous run
-            if ( runCount > 1 )
-            {
-                for ( int j = ( ( totalPut - numPerRun ) - ( numPerRun / 2 ) ); j < ( totalPut - numPerRun ); j++ )
-                {
-                    jcs.remove( String.valueOf( j ) );
-                }
-            }
-
-            Thread.sleep( pauseBetweenRuns );
-            if ( runCount % 100 == 0 )
-            {
-                System.out.println( LOG_DIVIDER );
-                System.out.println( "Elapsed " + timer.getElapsedTimeString() );
-                System.out.println( "Run count: " + runCount + " Average size: " + ( totalSize / totalPut ) + "\n"
-                    + jcs.getStats() );
-                logMemoryUsage();
-            }
-        }
-
-        Thread.sleep( 3000 );
-        System.out.println( jcs.getStats() );
-        logMemoryUsage();
-
-        Thread.sleep( 10000 );
-        System.out.println( jcs.getStats() );
-        logMemoryUsage();
-
-        System.gc();
-        Thread.sleep( 3000 );
-        System.gc();
-        System.out.println( jcs.getStats() );
-        logMemoryUsage();
-    }
-
-    /**
-     * Logs the memory usage.
-     */
-    private static void logMemoryUsage()
-    {
-        long byte2MB = 1024 * 1024;
-        long total = rt.totalMemory() / byte2MB;
-        long free = rt.freeMemory() / byte2MB;
-        long used = total - free;
-        System.out.println( LOG_DIVIDER );
-        System.out.println( "Memory:" + " Used:" + format.format( used ) + "MB" + " Free:" + format.format( free )
-            + "MB" + " Total:" + format.format( total ) + "MB" );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/LRUMapSizeVsCount.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/LRUMapSizeVsCount.java
deleted file mode 100644
index 587e462..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/indexed/LRUMapSizeVsCount.java
+++ /dev/null
@@ -1,236 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.indexed;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.Map;
-
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestSuite;
-
-/**
- * This ensures that the jcs version of the LRU map is as fast as the commons
- * version. It has been testing at .6 to .7 times the commons LRU.
- * <p>
- * @author aaronsm
- *
- */
-public class LRUMapSizeVsCount
-    extends TestCase
-{
-    /** The put put ration after the test */
-    double ratioPut = 0;
-
-    /** The ratio after the test */
-    double ratioGet = 0;
-
-    /** put size / count  ratio */
-    float targetPut = 1.2f;
-
-    /** get size / count ratio */
-    float targetGet = 1.2f;
-
-    /** Time to loop */
-    int loops = 20;
-
-    /** items to put and get per loop */
-    int tries = 100000;
-
-    /**
-     * @param testName
-     */
-    public LRUMapSizeVsCount( String testName )
-    {
-        super( testName );
-    }
-
-    /**
-     * A unit test suite for JUnit
-     * <p>
-     * @return The test suite
-     */
-    public static Test suite()
-    {
-        return new TestSuite( LRUMapSizeVsCount.class );
-    }
-
-    /**
-     * A unit test for JUnit
-     *
-     * @throws Exception
-     *                Description of the Exception
-     */
-    public void testSimpleLoad()
-        throws Exception
-    {
-        doWork();
-        assertTrue( this.ratioPut < targetPut );
-        assertTrue( this.ratioGet < targetGet );
-    }
-
-    /**
-     *
-     */
-    public void doWork()
-    {
-        long start = 0;
-        long end = 0;
-        long time = 0;
-        float tPer = 0;
-
-        long putTotalCount = 0;
-        long getTotalCount = 0;
-        long putTotalSize = 0;
-        long getTotalSize = 0;
-
-        long minTimeSizePut = Long.MAX_VALUE;
-        long minTimeSizeGet = Long.MAX_VALUE;
-        long minTimeCountPut = Long.MAX_VALUE;
-        long minTimeCountGet = Long.MAX_VALUE;
-
-        String cacheName = "LRUMap";
-        String cache2Name = "";
-
-        try
-        {
-        	IndexedDiskCacheAttributes cattr = new IndexedDiskCacheAttributes();
-        	cattr.setName("junit");
-        	cattr.setCacheName("junit");
-        	cattr.setDiskPath(".");
-        	IndexedDiskCache<String, String> idc = new IndexedDiskCache<>(cattr);
-
-			Map<String, IndexedDiskElementDescriptor> cacheCount = idc.new LRUMapCountLimited( tries );
-			Map<String, IndexedDiskElementDescriptor> cacheSize = idc.new LRUMapSizeLimited( tries/1024/2 );
-
-            for ( int j = 0; j < loops; j++ )
-            {
-                cacheName = "LRU Count           ";
-                start = System.currentTimeMillis();
-                for ( int i = 0; i < tries; i++ )
-                {
-                    cacheCount.put( "key:" + i,  new IndexedDiskElementDescriptor(i, i) );
-                }
-                end = System.currentTimeMillis();
-                time = end - start;
-                putTotalCount += time;
-                minTimeCountPut = Math.min(time, minTimeCountPut);
-                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
-                System.out.println( cacheName + " put time for " + tries + " = " + time + "; millis per = " + tPer );
-
-                start = System.currentTimeMillis();
-                for ( int i = 0; i < tries; i++ )
-                {
-                    cacheCount.get( "key:" + i );
-                }
-                end = System.currentTimeMillis();
-                time = end - start;
-                getTotalCount += time;
-                minTimeCountGet = Math.min(minTimeCountGet, time);
-                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
-                System.out.println( cacheName + " get time for " + tries + " = " + time + "; millis per = " + tPer );
-
-                ///////////////////////////////////////////////////////////////
-                cache2Name = "LRU Size            ";
-                //or LRUMapJCS
-                //cache2Name = "Hashtable";
-                //Hashtable cache2 = new Hashtable();
-                start = System.currentTimeMillis();
-                for ( int i = 0; i < tries; i++ )
-                {
-                    cacheSize.put( "key:" + i, new IndexedDiskElementDescriptor(i, i) );
-                }
-                end = System.currentTimeMillis();
-                time = end - start;
-                putTotalSize += time;
-                minTimeSizePut = Math.min(minTimeSizePut, time);
-
-                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
-                System.out.println( cache2Name + " put time for " + tries + " = " + time + "; millis per = " + tPer );
-
-                start = System.currentTimeMillis();
-                for ( int i = 0; i < tries; i++ )
-                {
-                    cacheSize.get( "key:" + i );
-                }
-                end = System.currentTimeMillis();
-                time = end - start;
-                getTotalSize += time;
-                minTimeSizeGet = Math.min(minTimeSizeGet, time);
-
-                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
-                System.out.println( cache2Name + " get time for " + tries + " = " + time + "; millis per = " + tPer );
-
-                System.out.println( "\n" );
-            }
-        }
-        catch ( Exception e )
-        {
-            e.printStackTrace( System.out );
-            System.out.println( e );
-        }
-
-        long putAvCount = putTotalCount / loops;
-        long getAvCount = getTotalCount / loops;
-        long putAvSize = putTotalSize / loops;
-        long getAvSize = getTotalSize / loops;
-
-        System.out.println( "Finished " + loops + " loops of " + tries + " gets and puts" );
-
-        System.out.println( "\n" );
-        System.out.println( "Put average for " + cacheName +  " = " + putAvCount );
-        System.out.println( "Put average for " + cache2Name + " = " + putAvSize );
-        ratioPut = (putAvSize *1.0) / putAvCount;
-        System.out.println( cache2Name.trim() + " puts took " + ratioPut + " times the " + cacheName.trim() + ", the goal is <" + targetPut
-            + "x" );
-
-        System.out.println( "\n" );
-        System.out.println( "Put minimum for " + cacheName +  " = " + minTimeCountPut );
-        System.out.println( "Put minimum for " + cache2Name + " = " + minTimeSizePut );
-        ratioPut = (minTimeSizePut * 1.0) / minTimeCountPut;
-        System.out.println( cache2Name.trim() + " puts took " + ratioPut + " times the " + cacheName.trim() + ", the goal is <" + targetPut
-            + "x" );
-
-        System.out.println( "\n" );
-        System.out.println( "Get average for " + cacheName + " = " + getAvCount );
-        System.out.println( "Get average for " + cache2Name + " = " + getAvSize );
-        ratioGet = Float.intBitsToFloat( (int) getAvCount ) / Float.intBitsToFloat( (int) getAvSize );
-        ratioGet = (getAvSize * 1.0) / getAvCount;
-        System.out.println( cache2Name.trim() + " gets took " + ratioGet + " times the " + cacheName.trim() + ", the goal is <" + targetGet
-            + "x" );
-
-        System.out.println( "\n" );
-        System.out.println( "Get minimum for " + cacheName +  " = " + minTimeCountGet );
-        System.out.println( "Get minimum for " + cache2Name + " = " + minTimeSizeGet );
-        ratioPut = (minTimeSizeGet * 1.0) / minTimeCountGet;
-        System.out.println( cache2Name.trim() + " puts took " + ratioPut + " times the " + cacheName.trim() + ", the goal is <" + targetGet
-            + "x" );
-
-    }
-
-    /**
-     * @param args
-     */
-    public static void main( String args[] )
-    {
-    	LRUMapSizeVsCount test = new LRUMapSizeVsCount( "command" );
-        test.doWork();
-    }
-
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/HsqlSetupTableUtil.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/HsqlSetupTableUtil.java
deleted file mode 100644
index 0929c7b..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/HsqlSetupTableUtil.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.jdbc;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.sql.Connection;
-import java.sql.SQLException;
-
-import org.apache.commons.jcs.auxiliary.disk.jdbc.hsql.HSQLDiskCacheFactory;
-
-/** Can use this to setup a table. */
-public class HsqlSetupTableUtil extends HSQLDiskCacheFactory
-{
-    /**
-     * SETUP a TABLE FOR CACHE testing
-     * <p>
-     * @param cConn
-     * @param tableName
-     *
-     * @throws SQLException if database problems occur
-     */
-    public static void setupTABLE( Connection cConn, String tableName ) throws SQLException
-    {
-        HsqlSetupTableUtil util = new HsqlSetupTableUtil();
-        util.setupTable(cConn, tableName);
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/JDBCDataSourceFactoryUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/JDBCDataSourceFactoryUnitTest.java
deleted file mode 100644
index 2068bdf..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/JDBCDataSourceFactoryUnitTest.java
+++ /dev/null
@@ -1,205 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.jdbc;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.sql.SQLException;
-import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.Map;
-import java.util.Properties;
-
-import javax.naming.Context;
-import javax.naming.InitialContext;
-import javax.naming.NamingException;
-import javax.naming.spi.InitialContextFactory;
-
-import org.apache.commons.dbcp2.BasicDataSource;
-import org.apache.commons.dbcp2.datasources.SharedPoolDataSource;
-import org.apache.commons.jcs.auxiliary.disk.jdbc.dsfactory.DataSourceFactory;
-import org.apache.commons.jcs.auxiliary.disk.jdbc.dsfactory.JndiDataSourceFactory;
-import org.apache.commons.jcs.auxiliary.disk.jdbc.dsfactory.SharedPoolDataSourceFactory;
-
-import junit.framework.TestCase;
-
-/** Unit tests for the data source factories */
-public class JDBCDataSourceFactoryUnitTest
-    extends TestCase
-{
-    /** Verify that we can configure the object based on the props.
-     *  @throws SQLException
-     */
-    public void testConfigureDataSourceFactory_Simple() throws SQLException
-    {
-        // SETUP
-        String poolName = "testConfigurePoolAccessAttributes_Simple";
-
-        String url = "adfads";
-        String userName = "zvzvz";
-        String password = "qewrrewq";
-        int maxActive = 10;
-        String driverClassName = "org.hsqldb.jdbcDriver";
-
-        Properties props = new Properties();
-        String prefix = JDBCDiskCacheFactory.POOL_CONFIGURATION_PREFIX
-    		+ poolName
-            + JDBCDiskCacheFactory.ATTRIBUTE_PREFIX;
-        props.put( prefix + ".url", url );
-        props.put( prefix + ".userName", userName );
-        props.put( prefix + ".password", password );
-        props.put( prefix + ".maxActive", String.valueOf( maxActive ) );
-        props.put( prefix + ".driverClassName", driverClassName );
-
-        JDBCDiskCacheFactory factory = new JDBCDiskCacheFactory();
-        factory.initialize();
-
-        JDBCDiskCacheAttributes cattr = new JDBCDiskCacheAttributes();
-        cattr.setConnectionPoolName( poolName );
-
-        // DO WORK
-        DataSourceFactory result = factory.getDataSourceFactory( cattr, props );
-        assertTrue("Should be a shared pool data source factory", result instanceof SharedPoolDataSourceFactory);
-
-        SharedPoolDataSource spds = (SharedPoolDataSource) result.getDataSource();
-        assertNotNull( "Should have a data source class", spds );
-
-        // VERIFY
-        assertEquals( "Wrong pool name", poolName, spds.getDescription() );
-        assertEquals( "Wrong maxActive value", maxActive, spds.getMaxTotal() );
-    }
-
-    /** Verify that we can configure the object based on the attributes.
-     *  @throws SQLException
-     */
-    public void testConfigureDataSourceFactory_Attributes() throws SQLException
-    {
-        // SETUP
-        String url = "adfads";
-        String userName = "zvzvz";
-        String password = "qewrrewq";
-        int maxActive = 10;
-        String driverClassName = "org.hsqldb.jdbcDriver";
-
-        JDBCDiskCacheFactory factory = new JDBCDiskCacheFactory();
-        factory.initialize();
-
-        JDBCDiskCacheAttributes cattr = new JDBCDiskCacheAttributes();
-        cattr.setUrl(url);
-        cattr.setUserName(userName);
-        cattr.setPassword(password);
-        cattr.setMaxTotal(maxActive);
-        cattr.setDriverClassName(driverClassName);
-
-        // DO WORK
-        DataSourceFactory result = factory.getDataSourceFactory( cattr, null );
-        assertTrue("Should be a shared pool data source factory", result instanceof SharedPoolDataSourceFactory);
-
-        SharedPoolDataSource spds = (SharedPoolDataSource) result.getDataSource();
-        assertNotNull( "Should have a data source class", spds );
-
-        // VERIFY
-        assertEquals( "Wrong maxActive value", maxActive, spds.getMaxTotal() );
-    }
-
-    /** Verify that we can configure the object based on JNDI.
-     *  @throws SQLException
-     */
-    public void testConfigureDataSourceFactory_JNDI() throws SQLException
-    {
-        // SETUP
-        String jndiPath = "java:comp/env/jdbc/MyDB";
-        long ttl = 300000L;
-
-        System.setProperty(Context.INITIAL_CONTEXT_FACTORY,
-                MockInitialContextFactory.class.getName());
-
-        MockInitialContextFactory.bind(jndiPath, new BasicDataSource());
-
-        JDBCDiskCacheFactory factory = new JDBCDiskCacheFactory();
-        factory.initialize();
-
-        JDBCDiskCacheAttributes cattr = new JDBCDiskCacheAttributes();
-        cattr.setJndiPath(jndiPath);
-        cattr.setJndiTTL(ttl);
-
-        // DO WORK
-        DataSourceFactory result = factory.getDataSourceFactory( cattr, null );
-        assertTrue("Should be a JNDI data source factory", result instanceof JndiDataSourceFactory);
-    }
-
-    /* For JNDI mocking */
-    public static class MockInitialContextFactory implements InitialContextFactory
-    {
-        private static Context context;
-
-        static
-        {
-            try
-            {
-                context = new InitialContext(true)
-                {
-                    Map<String, Object> bindings = new HashMap<>();
-
-                    @Override
-                    public void bind(String name, Object obj) throws NamingException
-                    {
-                        bindings.put(name, obj);
-                    }
-
-                    @Override
-                    public Object lookup(String name) throws NamingException
-                    {
-                        return bindings.get(name);
-                    }
-
-                    @Override
-                    public Hashtable<?, ?> getEnvironment() throws NamingException
-                    {
-                        return new Hashtable<>();
-                    }
-                };
-            }
-            catch (NamingException e)
-            {
-            	// can't happen.
-                throw new RuntimeException(e);
-            }
-        }
-
-        @Override
-		public Context getInitialContext(Hashtable<?, ?> environment) throws NamingException
-        {
-            return context;
-        }
-
-        public static void bind(String name, Object obj)
-        {
-            try
-            {
-                context.bind(name, obj);
-            }
-            catch (NamingException e)
-            {
-            	// can't happen.
-                throw new RuntimeException(e);
-            }
-        }
-    }
-}
-
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/JDBCDiskCacheRemovalUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/JDBCDiskCacheRemovalUnitTest.java
deleted file mode 100644
index a526d06..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/JDBCDiskCacheRemovalUnitTest.java
+++ /dev/null
@@ -1,109 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.jdbc;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.sql.Connection;
-import java.sql.DriverManager;
-import java.sql.SQLException;
-import java.util.Properties;
-
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-
-import junit.framework.TestCase;
-
-/** Tests for the removal functionality. */
-public class JDBCDiskCacheRemovalUnitTest
-    extends TestCase
-{
-    /** db name -- set in system props */
-    private final String databaseName = "JCS_STORE_REMOVAL";
-
-    /**
-     * Test setup
-     */
-    @Override
-    public void setUp()
-    {
-        System.setProperty( "DATABASE_NAME", databaseName );
-        JCS.setConfigFilename( "/TestJDBCDiskCacheRemoval.ccf" );
-    }
-
-    /**
-     * Verify the fix for BUG JCS-20
-     * <p>
-     * Setup an hsql db. Add an item. Remove using partial key.
-     * @throws Exception
-     */
-    public void testPartialKeyRemoval_Good()
-        throws Exception
-    {
-        // SETUP
-        setupDatabase();
-
-        String keyPart1 = "part1";
-        String keyPart2 = "part2";
-        String region = "testCache1";
-        String data = "adfadsfasfddsafasasd";
-
-        CacheAccess<String, String> jcs = JCS.getInstance( region );
-
-        // DO WORK
-        jcs.put( keyPart1 + ":" + keyPart2, data );
-        Thread.sleep( 1000 );
-
-        // VERIFY
-        String resultBeforeRemove = jcs.get( keyPart1 + ":" + keyPart2 );
-        assertEquals( "Wrong result", data, resultBeforeRemove );
-
-        jcs.remove( keyPart1 + ":" );
-        String resultAfterRemove = jcs.get( keyPart1 + ":" + keyPart2 );
-        assertNull( "Should not have a result after removal.", resultAfterRemove );
-
-//        System.out.println( jcs.getStats() );
-    }
-
-    /**
-     * Create the database.
-     * @throws InstantiationException
-     * @throws IllegalAccessException
-     * @throws ClassNotFoundException
-     * @throws SQLException
-     */
-    private void setupDatabase()
-        throws InstantiationException, IllegalAccessException, ClassNotFoundException, SQLException
-    {
-        System.setProperty( "hsqldb.cache_scale", "8" );
-
-        String rafroot = "target";
-        Properties p = new Properties();
-        String driver = p.getProperty( "driver", "org.hsqldb.jdbcDriver" );
-        String url = p.getProperty( "url", "jdbc:hsqldb:" );
-        String database = p.getProperty( "database", rafroot + "/JDBCDiskCacheRemovalUnitTest" );
-        String user = p.getProperty( "user", "sa" );
-        String password = p.getProperty( "password", "" );
-
-        new org.hsqldb.jdbcDriver();
-        Class.forName( driver ).newInstance();
-        Connection cConn = DriverManager.getConnection( url + database, user, password );
-
-        HsqlSetupTableUtil.setupTABLE( cConn, databaseName );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/JDBCDiskCacheSharedPoolUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/JDBCDiskCacheSharedPoolUnitTest.java
deleted file mode 100644
index b33ae4d..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/JDBCDiskCacheSharedPoolUnitTest.java
+++ /dev/null
@@ -1,141 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.jdbc;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-
-import java.sql.Connection;
-import java.sql.DriverManager;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
-
-/**
- * Runs basic tests for the JDBC disk cache using a shared connection pool.
- *<p>
- * @author Aaron Smuts
- */
-public class JDBCDiskCacheSharedPoolUnitTest
-    extends TestCase
-{
-    /** Test setup */
-    @Override
-    public void setUp()
-    {
-        JCS.setConfigFilename( "/TestJDBCDiskCacheSharedPool.ccf" );
-    }
-
-    /**
-     * Test the basic JDBC disk cache functionality with a hsql backing.
-     * @throws Exception
-     */
-    public void testSimpleJDBCPutGetWithHSQL()
-        throws Exception
-    {
-        System.setProperty( "hsqldb.cache_scale", "8" );
-
-        String rafroot = "target";
-        Properties p = new Properties();
-        String driver = p.getProperty( "driver", "org.hsqldb.jdbcDriver" );
-        String url = p.getProperty( "url", "jdbc:hsqldb:" );
-        String database = p.getProperty( "database", rafroot + "/cache_hsql_db_sharedpool" );
-        String user = p.getProperty( "user", "sa" );
-        String password = p.getProperty( "password", "" );
-
-        new org.hsqldb.jdbcDriver();
-        Class.forName( driver ).newInstance();
-        Connection cConn = DriverManager.getConnection( url + database, user, password );
-
-        HsqlSetupTableUtil.setupTABLE( cConn, "JCS_STORE_0" );
-
-        HsqlSetupTableUtil.setupTABLE( cConn, "JCS_STORE_1" );
-
-        runTestForRegion( "testCache1", 200 );
-    }
-
-    /**
-     * Adds items to cache, gets them, and removes them. The item count is more than the size of the
-     * memory cache, so items should spool to disk.
-     * <p>
-     * @param region Name of the region to access
-     * @param items
-     * @throws Exception If an error occurs
-     */
-    public void runTestForRegion( String region, int items )
-        throws Exception
-    {
-        CacheAccess<String, String> jcs = JCS.getInstance( region );
-
-//        System.out.println( "BEFORE PUT \n" + jcs.getStats() );
-
-        // Add items to cache
-
-        for ( int i = 0; i <= items; i++ )
-        {
-            jcs.put( i + ":key", region + " data " + i );
-        }
-
-//        System.out.println( jcs.getStats() );
-
-        Thread.sleep( 1000 );
-
-//        System.out.println( jcs.getStats() );
-
-        // Test that all items are in cache
-
-        for ( int i = 0; i <= items; i++ )
-        {
-            String value = jcs.get( i + ":key" );
-
-            assertEquals( "key = [" + i + ":key] value = [" + value + "]", region + " data " + i, value );
-        }
-
-        // Test that getElements returns all the expected values
-        Set<String> keys = new HashSet<>();
-        for ( int i = 0; i <= items; i++ )
-        {
-            keys.add( i + ":key" );
-        }
-
-        Map<String, ICacheElement<String, String>> elements = jcs.getCacheElements( keys );
-        for ( int i = 0; i <= items; i++ )
-        {
-            ICacheElement<String, String> element = elements.get( i + ":key" );
-            assertNotNull( "element " + i + ":key is missing", element );
-            assertEquals( "value " + i + ":key", region + " data " + i, element.getVal() );
-        }
-
-        // Remove all the items
-        for ( int i = 0; i <= items; i++ )
-        {
-            jcs.remove( i + ":key" );
-        }
-
-        // Verify removal
-        for ( int i = 0; i <= items; i++ )
-        {
-            assertNull( "Removed key should be null: " + i + ":key", jcs.get( i + ":key" ) );
-        }
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/JDBCDiskCacheShrinkUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/JDBCDiskCacheShrinkUnitTest.java
deleted file mode 100644
index 702a37b..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/JDBCDiskCacheShrinkUnitTest.java
+++ /dev/null
@@ -1,222 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.jdbc;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-
-import java.sql.Connection;
-import java.sql.DriverManager;
-import java.util.Properties;
-
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.access.exception.CacheException;
-import org.apache.commons.jcs.utils.timing.SleepUtil;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-/**
- * Runs basic tests for the JDBC disk cache.
- * <p>
- * @author Aaron Smuts
- */
-public class JDBCDiskCacheShrinkUnitTest
-{
-    /**
-     * Creates the DB
-     * <p>
-     * @throws Exception
-     */
-    @BeforeClass
-    public static void setupDatabase() throws Exception
-    {
-        System.setProperty( "hsqldb.cache_scale", "8" );
-
-        String rafroot = "target";
-        Properties p = new Properties();
-        String driver = p.getProperty( "driver", "org.hsqldb.jdbcDriver" );
-        String url = p.getProperty( "url", "jdbc:hsqldb:" );
-        String database = p.getProperty( "database", rafroot + "/JDBCDiskCacheShrinkUnitTest" );
-        String user = p.getProperty( "user", "sa" );
-        String password = p.getProperty( "password", "" );
-
-        new org.hsqldb.jdbcDriver();
-        Class.forName( driver ).newInstance();
-        Connection cConn = DriverManager.getConnection( url + database, user, password );
-
-        HsqlSetupTableUtil.setupTABLE( cConn, "JCS_STORE_SHRINK" );
-    }
-
-    /**
-     * Test setup
-     */
-    @Before
-    public void setUp()
-    {
-        JCS.setConfigFilename( "/TestJDBCDiskCacheShrink.ccf" );
-    }
-
-    /**
-     * Test the basic JDBC disk cache functionality with a hsql backing. Verify that items
-     * configured to expire after 1 second actually expire.
-     * <p>
-     * @throws Exception
-     */
-    @Test
-    public void testExpireInBackground()
-        throws Exception
-    {
-        String regionExpire = "expire1Second";
-        int items = 200;
-
-        CacheAccess<String, String> jcsExpire = JCS.getInstance( regionExpire );
-
-//        System.out.println( "BEFORE PUT \n" + jcsExpire.getStats() );
-
-        // Add items to cache
-
-        for ( int i = 0; i <= items; i++ )
-        {
-            jcsExpire.put( i + ":key", regionExpire + " data " + i );
-        }
-
-//        System.out.println( jcsExpire.getStats() );
-
-        // the shrinker is supposed to run every second
-        SleepUtil.sleepAtLeast( 3000 );
-
-//        System.out.println( jcsExpire.getStats() );
-
-        // Test that all items have been removed from the cache
-        for ( int i = 0; i <= items; i++ )
-        {
-            assertNull( "Removed key should be null: " + i + ":key", jcsExpire.get( i + ":key" ) );
-        }
-    }
-
-    /**
-     * Verify that those not scheduled to expire do not expire.
-     * <p>
-     * @throws CacheException
-     * @throws InterruptedException
-     */
-    @Test
-    public void testDidNotExpire()
-        throws CacheException, InterruptedException
-    {
-        String region = "expire100Second";
-        int items = 200;
-
-        CacheAccess<String, String> jcs = JCS.getInstance( region );
-
-//        System.out.println( "BEFORE PUT \n" + jcs.getStats() );
-
-        // Add items to cache
-
-        for ( int i = 0; i <= items; i++ )
-        {
-            jcs.put( i + ":key", region + " data " + i );
-        }
-
-//        System.out.println( jcs.getStats() );
-
-        SleepUtil.sleepAtLeast( 1000 );
-
-//        System.out.println( jcs.getStats() );
-
-        // Test that all items are in cache
-
-        for ( int i = 0; i <= items; i++ )
-        {
-            String value = jcs.get( i + ":key" );
-
-            assertEquals( "key = [" + i + ":key] value = [" + value + "]", region + " data " + i, value );
-        }
-
-        // Remove all the items
-
-        for ( int i = 0; i <= items; i++ )
-        {
-            jcs.remove( i + ":key" );
-        }
-
-        // Verify removal
-
-        for ( int i = 0; i <= items; i++ )
-        {
-            assertNull( "Removed key should be null: " + i + ":key", jcs.get( i + ":key" ) );
-        }
-    }
-
-    /**
-     * Verify that eternal trumps max life.
-     * @throws CacheException
-     * @throws InterruptedException
-     */
-    @Test
-    public void testDidNotExpireEternal()
-        throws CacheException, InterruptedException
-    {
-        String region = "eternal";
-        int items = 200;
-
-        CacheAccess<String, String> jcs = JCS.getInstance( region );
-
-//        System.out.println( "BEFORE PUT \n" + jcs.getStats() );
-
-        // Add items to cache
-
-        for ( int i = 0; i <= items; i++ )
-        {
-            jcs.put( i + ":key", region + " data " + i );
-        }
-
-//        System.out.println( jcs.getStats() );
-
-        SleepUtil.sleepAtLeast( 1000 );
-
-//        System.out.println( jcs.getStats() );
-
-        // Test that all items are in cache
-
-        for ( int i = 0; i <= items; i++ )
-        {
-            String value = jcs.get( i + ":key" );
-
-            assertEquals( "key = [" + i + ":key] value = [" + value + "]", region + " data " + i, value );
-        }
-
-        // Remove all the items
-
-        for ( int i = 0; i <= items; i++ )
-        {
-            jcs.remove( i + ":key" );
-        }
-
-        // Verify removal
-
-        for ( int i = 0; i <= items; i++ )
-        {
-            assertNull( "Removed key should be null: " + i + ":key", jcs.get( i + ":key" ) );
-        }
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/JDBCDiskCacheUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/JDBCDiskCacheUnitTest.java
deleted file mode 100644
index bd79ab9..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/JDBCDiskCacheUnitTest.java
+++ /dev/null
@@ -1,206 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.jdbc;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.sql.Connection;
-import java.sql.DriverManager;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
-import java.util.concurrent.Executors;
-
-import junit.framework.TestCase;
-
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.auxiliary.disk.jdbc.dsfactory.DataSourceFactory;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.control.MockCompositeCacheManager;
-import org.apache.commons.jcs.utils.serialization.StandardSerializer;
-import org.apache.commons.jcs.utils.threadpool.DaemonThreadFactory;
-
-/**
- * Runs basic tests for the JDBC disk cache.
- *<p>
- * @author Aaron Smuts
- */
-public class JDBCDiskCacheUnitTest
-    extends TestCase
-{
-    /** Test setup */
-    @Override
-    public void setUp()
-    {
-        JCS.setConfigFilename( "/TestJDBCDiskCache.ccf" );
-    }
-
-    /**
-     * Test the basic JDBC disk cache functionality with a hsql backing.
-     * @throws Exception
-     */
-    public void testSimpleJDBCPutGetWithHSQL()
-        throws Exception
-    {
-        System.setProperty( "hsqldb.cache_scale", "8" );
-
-        String rafroot = "target";
-        Properties p = new Properties();
-        String driver = p.getProperty( "driver", "org.hsqldb.jdbcDriver" );
-        String url = p.getProperty( "url", "jdbc:hsqldb:" );
-        String database = p.getProperty( "database", rafroot + "/cache_hsql_db" );
-        String user = p.getProperty( "user", "sa" );
-        String password = p.getProperty( "password", "" );
-
-        new org.hsqldb.jdbcDriver();
-        Class.forName( driver ).newInstance();
-        Connection cConn = DriverManager.getConnection( url + database, user, password );
-
-        HsqlSetupTableUtil.setupTABLE( cConn, "JCS_STORE2" );
-
-        runTestForRegion( "testCache1", 200 );
-    }
-
-    /**
-     * Adds items to cache, gets them, and removes them. The item count is more than the size of the
-     * memory cache, so items should spool to disk.
-     * <p>
-     * @param region Name of the region to access
-     * @param items
-     * @throws Exception If an error occurs
-     */
-    public void runTestForRegion( String region, int items )
-        throws Exception
-    {
-        CacheAccess<String, String> jcs = JCS.getInstance( region );
-
-//        System.out.println( "BEFORE PUT \n" + jcs.getStats() );
-
-        // Add items to cache
-
-        for ( int i = 0; i <= items; i++ )
-        {
-            jcs.put( i + ":key", region + " data " + i );
-        }
-
-//        System.out.println( jcs.getStats() );
-
-        Thread.sleep( 1000 );
-
-//        System.out.println( jcs.getStats() );
-
-        // Test that all items are in cache
-
-        for ( int i = 0; i <= items; i++ )
-        {
-            String value = jcs.get( i + ":key" );
-
-            assertEquals( "key = [" + i + ":key] value = [" + value + "]", region + " data " + i, value );
-        }
-
-        // Test that getElements returns all the expected values
-        Set<String> keys = new HashSet<>();
-        for ( int i = 0; i <= items; i++ )
-        {
-            keys.add( i + ":key" );
-        }
-
-        Map<String, ICacheElement<String, String>> elements = jcs.getCacheElements( keys );
-        for ( int i = 0; i <= items; i++ )
-        {
-            ICacheElement<String, String> element = elements.get( i + ":key" );
-            assertNotNull( "element " + i + ":key is missing", element );
-            assertEquals( "value " + i + ":key", region + " data " + i, element.getVal() );
-        }
-
-        // Remove all the items
-        for ( int i = 0; i <= items; i++ )
-        {
-            jcs.remove( i + ":key" );
-        }
-
-        // Verify removal
-        for ( int i = 0; i <= items; i++ )
-        {
-            assertNull( "Removed key should be null: " + i + ":key", jcs.get( i + ":key" ) );
-        }
-    }
-
-    /**
-     * Verfiy that it uses the pool access manager config.
-     * <p>
-     * @throws Exception
-     */
-    public void testInitializePoolAccess_withPoolName()
-        throws Exception
-    {
-        // SETUP
-        String poolName = "testInitializePoolAccess_withPoolName";
-
-        String url = "jdbc:hsqldb:";
-        String userName = "sa";
-        String password = "";
-        int maxActive = 10;
-        String driverClassName = "org.hsqldb.jdbcDriver";
-
-        Properties props = new Properties();
-        String prefix = JDBCDiskCacheFactory.POOL_CONFIGURATION_PREFIX
-    		+ poolName
-            + JDBCDiskCacheFactory.ATTRIBUTE_PREFIX;
-        props.put( prefix + ".url", url );
-        props.put( prefix + ".userName", userName );
-        props.put( prefix + ".password", password );
-        props.put( prefix + ".maxActive", String.valueOf( maxActive ) );
-        props.put( prefix + ".driverClassName", driverClassName );
-
-        JDBCDiskCacheAttributes cattr = new JDBCDiskCacheAttributes();
-        cattr.setConnectionPoolName( poolName );
-        cattr.setTableName("JCSTESTTABLE_InitializePoolAccess");
-
-        MockCompositeCacheManager compositeCacheManager = new MockCompositeCacheManager();
-        compositeCacheManager.setConfigurationProperties( props );
-        JDBCDiskCacheFactory dcFactory = new JDBCDiskCacheFactory();
-        dcFactory.initialize();
-        dcFactory.setScheduledExecutorService(Executors.newScheduledThreadPool(2,
-        	new DaemonThreadFactory("JCS-JDBCDiskCacheManager-", Thread.MIN_PRIORITY)));
-
-        JDBCDiskCache<String, String> diskCache = dcFactory.createCache( cattr, compositeCacheManager, null, new StandardSerializer() );
-        assertNotNull( "Should have a cache instance", diskCache );
-
-        // DO WORK
-        DataSourceFactory result = dcFactory.getDataSourceFactory(cattr, props);
-
-        // VERIFY
-        assertNotNull( "Should have a data source factory class", result );
-        assertEquals( "wrong name", poolName, result.getName() );
-
-        System.setProperty( "hsqldb.cache_scale", "8" );
-
-        String rafroot = "target";
-        String database = rafroot + "/cache_hsql_db";
-
-        new org.hsqldb.jdbcDriver();
-        Class.forName( driverClassName ).newInstance();
-        Connection cConn = DriverManager.getConnection( url + database, userName, password );
-
-        HsqlSetupTableUtil.setupTABLE( cConn, "JCSTESTTABLE_InitializePoolAccess" );
-
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/hsql/HSQLDiskCacheConcurrentUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/hsql/HSQLDiskCacheConcurrentUnitTest.java
deleted file mode 100644
index 4a666b8..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/hsql/HSQLDiskCacheConcurrentUnitTest.java
+++ /dev/null
@@ -1,161 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.jdbc.hsql;
-
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.extensions.ActiveTestSuite;
-import junit.framework.Test;
-import junit.framework.TestCase;
-
-/**
- * Test which exercises the indexed disk cache. This one uses three different regions for thre
- * threads.
- */
-public class HSQLDiskCacheConcurrentUnitTest
-    extends TestCase
-{
-    /**
-     * Number of items to cache, twice the configured maxObjects for the memory cache regions.
-     */
-    private static int items = 100;
-
-    /**
-     * Constructor for the TestDiskCache object.
-     * @param testName
-     */
-    public HSQLDiskCacheConcurrentUnitTest( String testName )
-    {
-        super( testName );
-    }
-
-    /**
-     * A unit test suite for JUnit. Uses ActiveTestSuite to run multiple tests concurrently.
-     * <p>
-     * @return The test suite
-     */
-    public static Test suite()
-    {
-        ActiveTestSuite suite = new ActiveTestSuite();
-
-        suite.addTest( new HSQLDiskCacheConcurrentUnitTest( "testHSQLDiskCache1" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "indexedRegion1" );
-            }
-        } );
-
-        suite.addTest( new HSQLDiskCacheConcurrentUnitTest( "testHSQLDiskCache2" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "indexedRegion2" );
-            }
-        } );
-
-        suite.addTest( new HSQLDiskCacheConcurrentUnitTest( "testHSQLDiskCache3" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "indexedRegion3" );
-            }
-        } );
-
-        return suite;
-    }
-
-    /**
-     * Test setup
-     */
-    @Override
-    public void setUp()
-    {
-        JCS.setConfigFilename( "/TestHSQLDiskCacheConcurrent.ccf" );
-    }
-
-    /**
-     * Adds items to cache, gets them, and removes them. The item count is more than the size of the
-     * memory cache, so items should spool to disk.
-     * <p>
-     * @param region Name of the region to access
-     * @throws Exception If an error occurs
-     */
-    public void runTestForRegion( String region )
-        throws Exception
-    {
-        CacheAccess<String, String> jcs = JCS.getInstance( region );
-
-        // Add items to cache
-        for ( int i = 0; i <= items; i++ )
-        {
-            jcs.put( i + ":key", region + " data " + i );
-        }
-
-//        System.out.println( jcs.getStats() );
-
-        // Test that all items are in cache
-        for ( int i = 0; i <= items; i++ )
-        {
-            String value = jcs.get( i + ":key" );
-
-            assertEquals( "key = [" + i + ":key] value = [" + value + "]", region + " data " + i, value );
-        }
-
-        // Test that getElements returns all the expected values
-        Set<String> keys = new HashSet<>();
-        for ( int i = 0; i <= items; i++ )
-        {
-            keys.add( i + ":key" );
-        }
-
-        Map<String, ICacheElement<String, String>> elements = jcs.getCacheElements( keys );
-        for ( int i = 0; i <= items; i++ )
-        {
-            ICacheElement<String, String> element = elements.get( i + ":key" );
-            assertNotNull( "element " + i + ":key is missing", element );
-            assertEquals( "value " + i + ":key", region + " data " + i, element.getVal() );
-        }
-
-        // Remove all the items
-        for ( int i = 0; i <= items; i++ )
-        {
-            jcs.remove( i + ":key" );
-        }
-
-        // Verify removal
-        for ( int i = 0; i <= items; i++ )
-        {
-            assertNull( "Removed key should be null: " + i + ":key", jcs.get( i + ":key" ) );
-        }
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/hsql/HSQLDiskCacheUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/hsql/HSQLDiskCacheUnitTest.java
deleted file mode 100644
index 58d7e77..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/hsql/HSQLDiskCacheUnitTest.java
+++ /dev/null
@@ -1,173 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.jdbc.hsql;
-
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.access.exception.CacheException;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-
-/**
- * Test which exercises the HSQL cache.
- */
-public class HSQLDiskCacheUnitTest
-    extends TestCase
-{
-    /**
-     * Test setup
-     */
-    @Override
-    public void setUp()
-    {
-        JCS.setConfigFilename( "/TestHSQLDiskCache.ccf" );
-    }
-
-    /**
-     * Adds items to cache, gets them, and removes them. The item count is more than the size of the
-     * memory cache, so items should spool to disk.
-     * <p>
-     * @throws Exception If an error occurs
-     */
-    public void testBasicPutRemove()
-        throws Exception
-    {
-        int items = 20;
-
-        String region = "testBasicPutRemove";
-
-        CacheAccess<String, String> jcs = JCS.getInstance( region );
-
-        // Add items to cache
-        for ( int i = 0; i <= items; i++ )
-        {
-            jcs.put( i + ":key", region + " data " + i );
-        }
-
-        // Test that all items are in cache
-        for ( int i = 0; i <= items; i++ )
-        {
-            String value = jcs.get( i + ":key" );
-            assertEquals( "key = [" + i + ":key] value = [" + value + "]", region + " data " + i, value );
-        }
-
-        // Test that getElements returns all the expected values
-        Set<String> keys = new HashSet<>();
-        for ( int i = 0; i <= items; i++ )
-        {
-            keys.add( i + ":key" );
-        }
-
-        Map<String, ICacheElement<String, String>> elements = jcs.getCacheElements( keys );
-        for ( int i = 0; i <= items; i++ )
-        {
-            ICacheElement<String, String> element = elements.get( i + ":key" );
-            assertNotNull( "element " + i + ":key is missing", element );
-            assertEquals( "value " + i + ":key", region + " data " + i, element.getVal() );
-        }
-
-        // Remove all the items
-        for ( int i = 0; i <= items; i++ )
-        {
-            jcs.remove( i + ":key" );
-        }
-
-        // Verify removal
-        for ( int i = 0; i <= items; i++ )
-        {
-            assertNull( "Removed key should be null: " + i + ":key", jcs.get( i + ":key" ) );
-        }
-    }
-
-    /**
-     * Verify that remove all work son a region where it is not prohibited.
-     * <p>
-     * @throws CacheException
-     * @throws InterruptedException
-     */
-    public void testRemoveAll()
-        throws CacheException, InterruptedException
-    {
-        String region = "removeAllAllowed";
-        CacheAccess<String, String> jcs = JCS.getInstance( region );
-
-        int items = 20;
-
-        // Add items to cache
-        for ( int i = 0; i <= items; i++ )
-        {
-            jcs.put( i + ":key", region + " data " + i );
-        }
-
-        // a db thread could be updating when we call remove all?
-        // there was a race on remove all, an element may be put to disk after it is called even
-        // though the put
-        // was called before clear.
-        // I discovered it and removed it.
-        // Thread.sleep( 500 );
-
-//        System.out.println( jcs.getStats() );
-
-        jcs.clear();
-
-        for ( int i = 0; i <= items; i++ )
-        {
-            String value = jcs.get( i + ":key" );
-            assertNull( "value should be null key = [" + i + ":key] value = [" + value + "]", value );
-        }
-    }
-
-    /**
-     * Verify that remove all does not work on a region where it is prohibited.
-     * <p>
-     * @throws CacheException
-     * @throws InterruptedException
-     */
-    public void testRemoveAllProhibition()
-        throws CacheException, InterruptedException
-    {
-        String region = "noRemoveAll";
-        CacheAccess<String, String> jcs = JCS.getInstance( region );
-
-        int items = 20;
-
-        // Add items to cache
-        for ( int i = 0; i <= items; i++ )
-        {
-            jcs.put( i + ":key", region + " data " + i );
-        }
-
-        // a db thread could be updating the disk when
-        // Thread.sleep( 500 );
-
-        jcs.clear();
-
-        for ( int i = 0; i <= items; i++ )
-        {
-            String value = jcs.get( i + ":key" );
-            assertEquals( "key = [" + i + ":key] value = [" + value + "]", region + " data " + i, value );
-        }
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/mysql/MySQLDiskCacheHsqlBackedUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/mysql/MySQLDiskCacheHsqlBackedUnitTest.java
deleted file mode 100644
index 7030397..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/mysql/MySQLDiskCacheHsqlBackedUnitTest.java
+++ /dev/null
@@ -1,178 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.jdbc.mysql;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-
-import java.sql.Connection;
-import java.sql.DriverManager;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.auxiliary.disk.jdbc.HsqlSetupTableUtil;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-/**
- * Runs basic tests for the JDBC disk cache.
- * @author Aaron Smuts
- */
-public class MySQLDiskCacheHsqlBackedUnitTest
-{
-    /**
-     * Creates the DB
-     * <p>
-     * @throws Exception
-     */
-    @BeforeClass
-    public static void setupDatabase() throws Exception
-    {
-        System.setProperty( "hsqldb.cache_scale", "8" );
-
-        String rafroot = "target";
-        String url = "jdbc:hsqldb:";
-        String database = rafroot + "/MySQLDiskCacheHsqlBackedUnitTest";
-        String user = "sa";
-        String password = "";
-
-        new org.hsqldb.jdbcDriver();
-        Connection cConn = DriverManager.getConnection( url + database, user, password );
-
-        HsqlSetupTableUtil.setupTABLE( cConn, "JCS_STORE_MYSQL" );
-    }
-
-    /**
-     * Test setup
-     */
-    @Before
-    public void setUp()
-    {
-        JCS.setConfigFilename( "/TestMySQLDiskCache.ccf" );
-    }
-
-    /**
-     * Test the basic JDBC disk cache functionality with a hsql backing.
-     * @throws Exception
-     */
-    @Test
-    public void testSimpleJDBCPutGetWithHSQL()
-        throws Exception
-    {
-        runTestForRegion( "testCache1", 200 );
-    }
-
-    /**
-     * Adds items to cache, gets them, and removes them. The item count is more than the size of the
-     * memory cache, so items should spool to disk.
-     * <p>
-     * @param region Name of the region to access
-     * @param items
-     * @throws Exception If an error occurs
-     */
-    public void runTestForRegion( String region, int items )
-        throws Exception
-    {
-        CacheAccess<String, String> jcs = JCS.getInstance( region );
-        //System.out.println( "BEFORE PUT \n" + jcs.getStats() );
-
-        // Add items to cache
-        for ( int i = 0; i < items; i++ )
-        {
-            jcs.put( i + ":key", region + " data " + i );
-        }
-
-        //System.out.println( jcs.getStats() );
-        Thread.sleep( 1000 );
-        //System.out.println( jcs.getStats() );
-
-        // Test that all items are in cache
-        for ( int i = 0; i < items; i++ )
-        {
-            String value = jcs.get( i + ":key" );
-
-            assertEquals( "key = [" + i + ":key] value = [" + value + "]", region + " data " + i, value );
-        }
-
-        // Test that getElements returns all the expected values
-        Set<String> keys = new HashSet<>();
-        for ( int i = 0; i < items; i++ )
-        {
-            keys.add( i + ":key" );
-        }
-
-        Map<String, ICacheElement<String, String>> elements = jcs.getCacheElements( keys );
-        for ( int i = 0; i < items; i++ )
-        {
-            ICacheElement<String, String> element = elements.get( i + ":key" );
-            assertNotNull( "element " + i + ":key is missing", element );
-            assertEquals( "value " + i + ":key", region + " data " + i, element.getVal() );
-        }
-
-        // Remove all the items
-        for ( int i = 0; i < items; i++ )
-        {
-            jcs.remove( i + ":key" );
-        }
-
-        // Verify removal
-
-        for ( int i = 0; i < items; i++ )
-        {
-            assertNull( "Removed key should be null: " + i + ":key", jcs.get( i + ":key" ) );
-        }
-    }
-
-    /**
-     * Test the basic JDBC disk cache functionality with a hsql backing.
-     * <p>
-     * @throws Exception
-     */
-    @Test
-    public void testPutGetMatchingWithHSQL()
-        throws Exception
-    {
-        // SETUP
-        int items = 200;
-        String region = "testCache2";
-        CacheAccess<String, String> jcs = JCS.getInstance( region );
-//        System.out.println( "BEFORE PUT \n" + jcs.getStats() );
-
-        // DO WORK
-        for ( int i = 0; i < items; i++ )
-        {
-            jcs.put( i + ":key", region + " data " + i );
-        }
-        Thread.sleep( 1000 );
-
-        Map<String, ICacheElement<String, String>> matchingResults = jcs.getMatchingCacheElements( "1.8.+" );
-
-        // VERIFY
-        assertEquals( "Wrong number returned", 10, matchingResults.size() );
-//        System.out.println( "matchingResults.keySet() " + matchingResults.keySet() );
-//        System.out.println( "\nAFTER TEST \n" + jcs.getStats() );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/mysql/MySQLDiskCacheUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/mysql/MySQLDiskCacheUnitTest.java
deleted file mode 100644
index 596252c..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/mysql/MySQLDiskCacheUnitTest.java
+++ /dev/null
@@ -1,72 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.jdbc.mysql;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.sql.SQLException;
-
-import junit.framework.TestCase;
-
-import org.apache.commons.jcs.auxiliary.disk.jdbc.TableState;
-import org.apache.commons.jcs.auxiliary.disk.jdbc.dsfactory.SharedPoolDataSourceFactory;
-import org.apache.commons.jcs.engine.control.CompositeCacheManager;
-
-/**
- * Simple tests for the MySQLDisk Cache.
- * <p>
- * We will probably need to setup an hsql behind this, to test some of the pass through methods.
- * <p>
- * @author Aaron Smuts
- */
-public class MySQLDiskCacheUnitTest
-    extends TestCase
-{
-    /**
-     * Verify that we simply return null on get if an optimization is in
-     * progress and the cache is configured to balk on optimization.
-     * <p>
-     * This is a bit tricky since we don't want to have to have a mysql instance
-     * running. Right now this doesn't really test much
-     * @throws SQLException
-     */
-    public void testBalkOnGet() throws SQLException
-    {
-        // SETUP
-        MySQLDiskCacheAttributes attributes = new MySQLDiskCacheAttributes();
-        String tableName = "JCS_TEST";
-        // Just use something that exists
-        attributes.setDriverClassName( "org.hsqldb.jdbcDriver" );
-        attributes.setTableName( tableName );
-        attributes.setBalkDuringOptimization( true );
-        SharedPoolDataSourceFactory dsFactory = new SharedPoolDataSourceFactory();
-        dsFactory.initialize(attributes);
-
-        TableState tableState = new TableState( tableName );
-        tableState.setState( TableState.OPTIMIZATION_RUNNING );
-
-        MySQLDiskCache<String, String> cache = new MySQLDiskCache<>( attributes, dsFactory, tableState,
-        		CompositeCacheManager.getUnconfiguredInstance() );
-
-        // DO WORK
-        Object result = cache.processGet( "myKey" );
-
-        // VERIFY
-        assertNull( "The result should be null", result );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/mysql/util/ScheduleParserUtilUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/mysql/util/ScheduleParserUtilUnitTest.java
deleted file mode 100644
index 14511d5..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/disk/jdbc/mysql/util/ScheduleParserUtilUnitTest.java
+++ /dev/null
@@ -1,129 +0,0 @@
-package org.apache.commons.jcs.auxiliary.disk.jdbc.mysql.util;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.text.ParseException;
-import java.util.Date;
-
-import junit.framework.TestCase;
-
-/**
- * Unit tests for the schedule parser.
- * <p>
- * @author Aaron Smuts
- */
-public class ScheduleParserUtilUnitTest
-    extends TestCase
-{
-
-    /**
-     * Verify that we get an exception and not a null pointer for null input.
-     */
-    public void testGetDatesWithNullInput()
-    {
-        try
-        {
-            ScheduleParser.createDatesForSchedule( null );
-
-            fail( "Should have thrown an exception" );
-        }
-        catch ( ParseException e )
-        {
-            // expected
-        }
-    }
-
-    /**
-     * Verify that we get an exception and not a null pointer for null input.
-     */
-    public void testGetDateWithNullInput()
-    {
-        try
-        {
-            ScheduleParser.getDateForSchedule( null );
-
-            fail( "Should have thrown an exception" );
-        }
-        catch ( ParseException e )
-        {
-            // expected
-        }
-    }
-
-    /**
-     * Verify that we get one date for one date.
-     * @throws ParseException
-     */
-    public void testGetsDatesSingle()
-        throws ParseException
-    {
-        String schedule = "12:34:56";
-        Date[] dates = ScheduleParser.createDatesForSchedule( schedule );
-
-        assertEquals( "Wrong number of dates returned.", 1, dates.length );
-    }
-    /**
-     * Verify that we get one date for one date.
-     * @throws ParseException
-     */
-    public void testGetsDatesMultiple()
-        throws ParseException
-    {
-        String schedule = "12:34:56,03:51:00,12:34:12";
-        Date[] dates = ScheduleParser.createDatesForSchedule( schedule );
-        //System.out.println( dates );
-        assertEquals( "Wrong number of dates returned.", 3, dates.length );
-    }
-
-    /**
-     * Verify that we get an exception for a single bad date in a list.
-     */
-    public void testGetDatesMalformedNoColon()
-    {
-        try
-        {
-            String schedule = "12:34:56,03:51:00,123234";
-            ScheduleParser.createDatesForSchedule( schedule );
-
-            fail( "Should have thrown an exception for a malformed date" );
-        }
-        catch ( ParseException e )
-        {
-            // expected
-        }
-    }
-    /**
-     * Verify that we get an exception for a schedule that has a non numeric item.
-     */
-    public void testGetDatesMalformedNan()
-    {
-        try
-        {
-            String schedule = "12:34:56,03:51:00,aa:12:12";
-            ScheduleParser.createDatesForSchedule( schedule );
-
-            fail( "Should have thrown an exception for a malformed date" );
-        }
-        catch ( ParseException e )
-        {
-            // expected
-        }
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/lateral/LateralCacheNoWaitFacadeUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/lateral/LateralCacheNoWaitFacadeUnitTest.java
deleted file mode 100644
index c36bd4f..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/lateral/LateralCacheNoWaitFacadeUnitTest.java
+++ /dev/null
@@ -1,143 +0,0 @@
-package org.apache.commons.jcs.auxiliary.lateral;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.auxiliary.lateral.behavior.ILateralCacheAttributes;
-
-/**
- * Tests for LateralCacheNoWaitFacade.
- */
-public class LateralCacheNoWaitFacadeUnitTest
-    extends TestCase
-{
-    /**
-     * Verify that we can remove an item.
-     */
-    public void testAddThenRemoveNoWait_InList()
-    {
-        // SETUP
-        @SuppressWarnings("unchecked")
-        LateralCacheNoWait<String, String>[] noWaits = new LateralCacheNoWait[0];
-        ILateralCacheAttributes cattr = new LateralCacheAttributes();
-        cattr.setCacheName( "testCache1" );
-
-        LateralCacheNoWaitFacade<String, String> facade = new LateralCacheNoWaitFacade<>( null, noWaits, cattr );
-
-        LateralCache<String, String> cache = new LateralCache<>( cattr );
-        LateralCacheNoWait<String, String> noWait = new LateralCacheNoWait<>( cache );
-
-        // DO WORK
-        facade.addNoWait( noWait );
-
-        // VERIFY
-        assertTrue( "Should be in the list.", facade.containsNoWait( noWait ) );
-
-        // DO WORK
-        facade.removeNoWait( noWait );
-
-        // VERIFY
-        assertEquals( "Should have 0", 0, facade.noWaits.length );
-        assertFalse( "Should not be in the list. ", facade.containsNoWait( noWait ) );
-    }
-
-    /**
-     * Verify that we can remove an item.
-     */
-    public void testAddThenRemoveNoWait_InListSize2()
-    {
-        // SETUP
-        @SuppressWarnings("unchecked")
-        LateralCacheNoWait<String, String>[] noWaits = new LateralCacheNoWait[0];
-        ILateralCacheAttributes cattr = new LateralCacheAttributes();
-        cattr.setCacheName( "testCache1" );
-
-        LateralCacheNoWaitFacade<String, String> facade = new LateralCacheNoWaitFacade<>( null, noWaits, cattr );
-
-        LateralCache<String, String> cache = new LateralCache<>( cattr );
-        LateralCacheNoWait<String, String> noWait = new LateralCacheNoWait<>( cache );
-        LateralCacheNoWait<String, String> noWait2 = new LateralCacheNoWait<>( cache );
-
-        // DO WORK
-        facade.addNoWait( noWait );
-        facade.addNoWait( noWait2 );
-
-        // VERIFY
-        assertEquals( "Should have 2", 2, facade.noWaits.length );
-        assertTrue( "Should be in the list.", facade.containsNoWait( noWait ) );
-        assertTrue( "Should be in the list.", facade.containsNoWait( noWait2 ) );
-
-        // DO WORK
-        facade.removeNoWait( noWait );
-
-        // VERIFY
-        assertEquals( "Should only have 1", 1, facade.noWaits.length );
-        assertFalse( "Should not be in the list. ", facade.containsNoWait( noWait ) );
-        assertTrue( "Should be in the list.", facade.containsNoWait( noWait2 ) );
-    }
-
-    /**
-     * Verify that we can remove an item.
-     */
-    public void testAdd_InList()
-    {
-        // SETUP
-        @SuppressWarnings("unchecked")
-        LateralCacheNoWait<String, String>[] noWaits = new LateralCacheNoWait[0];
-        ILateralCacheAttributes cattr = new LateralCacheAttributes();
-        cattr.setCacheName( "testCache1" );
-
-        LateralCacheNoWaitFacade<String, String> facade = new LateralCacheNoWaitFacade<>( null, noWaits, cattr );
-
-        LateralCache<String, String> cache = new LateralCache<>( cattr );
-        LateralCacheNoWait<String, String> noWait = new LateralCacheNoWait<>( cache );
-
-        // DO WORK
-        facade.addNoWait( noWait );
-        facade.addNoWait( noWait );
-
-        // VERIFY
-        assertTrue( "Should be in the list.", facade.containsNoWait( noWait ) );
-        assertEquals( "Should only have 1", 1, facade.noWaits.length );
-    }
-
-    /**
-     * Verify that we can remove an item.
-     */
-    public void testAddThenRemoveNoWait_NotInList()
-    {
-        // SETUP
-        @SuppressWarnings("unchecked")
-        LateralCacheNoWait<String, String>[] noWaits = new LateralCacheNoWait[0];
-        ILateralCacheAttributes cattr = new LateralCacheAttributes();
-        cattr.setCacheName( "testCache1" );
-
-        LateralCacheNoWaitFacade<String, String> facade = new LateralCacheNoWaitFacade<>( null, noWaits, cattr );
-
-        LateralCache<String, String> cache = new LateralCache<>( cattr );
-        LateralCacheNoWait<String, String> noWait = new LateralCacheNoWait<>( cache );
-
-        // DO WORK
-        facade.removeNoWait( noWait );
-
-        // VERIFY
-        assertFalse( "Should not be in the list.", facade.containsNoWait( noWait ) );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/lateral/http/broadcast/LateralCacheTester.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/lateral/http/broadcast/LateralCacheTester.java
deleted file mode 100644
index 2326dff..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/lateral/http/broadcast/LateralCacheTester.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package org.apache.commons.jcs.auxiliary.lateral.http.broadcast;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * @author Aaron Smuts
- * @version 1.0
- */
-public class LateralCacheTester
-{
-
-    //    /** Description of the Method */
-    //    public static void main( String args[] )
-    //    {
-    //
-    //        String[] servers = {"10.1.17.109", "10.1.17.108"};
-    //
-    //        try
-    //        {
-    //
-    //            //for ( int i=0; i <100; i++ ) {
-    //            String val = "test object value";
-    //            LateralCacheThread dct = new LateralCacheThread( "testTable", "testkey",
-    // val, servers );
-    //            dct.setPriority( Thread.NORM_PRIORITY - 1 );
-    //            dct.start();
-    //
-    //            String val2 = "test object value2";
-    //            LateralCacheThread dct2 = new LateralCacheThread( "testTable", "testkey",
-    // val, servers );
-    //            dct2.setPriority( Thread.NORM_PRIORITY - 1 );
-    //            dct2.start();
-    //            //}
-    //
-    //        }
-    //        catch ( Exception e )
-    //        {
-    //            System.out.println( e.toString() );
-    //        }
-    //
-    //    }
-
-}
-// end class
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/LateralTCPConcurrentRandomTestUtil.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/LateralTCPConcurrentRandomTestUtil.java
deleted file mode 100644
index ac2b159..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/LateralTCPConcurrentRandomTestUtil.java
+++ /dev/null
@@ -1,195 +0,0 @@
-package org.apache.commons.jcs.auxiliary.lateral.socket.tcp;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-
-import java.util.Random;
-
-/**
- * @author Aaron Smuts
- */
-public class LateralTCPConcurrentRandomTestUtil
-    extends TestCase
-{
-    /** Should we write out. */
-    private static boolean isSysOut = false;
-    //private static boolean isSysOut = true;
-
-    /**
-     * Constructor for the TestDiskCache object.
-     *
-     * @param testName
-     */
-    public LateralTCPConcurrentRandomTestUtil( String testName )
-    {
-        super( testName );
-    }
-
-    /**
-     * Test setup
-     */
-    @Override
-    public void setUp()
-    {
-        JCS.setConfigFilename( "/TestTCPLateralCacheConcurrent.ccf" );
-    }
-
-    /**
-     * Randomly adds items to cache, gets them, and removes them. The range
-     * count is more than the size of the memory cache, so items should spool to
-     * disk.
-     * <p>
-     * @param region
-     *            Name of the region to access
-     * @param range
-     * @param numOps
-     * @param testNum
-     *
-     * @throws Exception
-     *                If an error occurs
-     */
-    public void runTestForRegion( String region, int range, int numOps, int testNum )
-        throws Exception
-    {
-        boolean show = true;//false;
-
-        CacheAccess<String, String> cache = JCS.getInstance( region );
-
-        TCPLateralCacheAttributes lattr2 = new TCPLateralCacheAttributes();
-        lattr2.setTcpListenerPort( 1103 );
-        lattr2.setTransmissionTypeName( "TCP" );
-        lattr2.setTcpServer( "localhost:1102" );
-
-        // this service will put and remove using the lateral to
-        // the cache instance above
-        // the cache thinks it is different since the listenerid is different
-        LateralTCPService<String, String> service = new LateralTCPService<>( lattr2 );
-        service.setListenerId( 123456 );
-
-        try
-        {
-            for ( int i = 1; i < numOps; i++ )
-            {
-                Random ran = new Random( i );
-                int n = ran.nextInt( 4 );
-                int kn = ran.nextInt( range );
-                String key = "key" + kn;
-                if ( n == 1 )
-                {
-                    ICacheElement<String, String> element = new CacheElement<>( region, key, region + ":data" + i
-                        + " junk asdfffffffadfasdfasf " + kn + ":" + n );
-                    service.update( element );
-                    if ( show )
-                    {
-                        p( "put " + key );
-                    }
-                }
-                /**/
-                else if ( n == 2 )
-                {
-                    service.remove( region, key );
-                    if ( show )
-                    {
-                        p( "removed " + key );
-                    }
-                }
-                /**/
-                else
-                {
-                    // slightly greater chance of get
-                    try
-                    {
-                        Object obj = service.get( region, key );
-                        if ( show && obj != null )
-                        {
-                            p( obj.toString() );
-                        }
-                    }
-                    catch ( Exception e )
-                    {
-                        // consider failing, some timeouts are expected
-                        e.printStackTrace();
-                    }
-                }
-
-                if ( i % 100 == 0 )
-                {
-                    p( cache.getStats() );
-                }
-
-            }
-            p( "Finished random cycle of " + numOps );
-        }
-        catch ( Exception e )
-        {
-            p( e.toString() );
-            e.printStackTrace( System.out );
-            throw e;
-        }
-
-        CacheAccess<String, String> jcs = JCS.getInstance( region );
-        String key = "testKey" + testNum;
-        String data = "testData" + testNum;
-        jcs.put( key, data );
-        String value = jcs.get( key );
-        assertEquals( "Couldn't put normally.", data, value );
-
-        // make sure the items we can find are in the correct region.
-        for ( int i = 1; i < numOps; i++ )
-        {
-            String keyL = "key" + i;
-            String dataL = jcs.get( keyL );
-            if ( dataL != null )
-            {
-                assertTrue( "Incorrect region detected.", dataL.startsWith( region ) );
-            }
-
-        }
-
-        //Thread.sleep( 1000 );
-
-        //ICacheElement<String, String> element = new CacheElement( region, "abc", "testdata");
-        //service.update( element );
-
-        //Thread.sleep( 2500 );
-        // could be too mcuh going on right now to get ti through, sot he test
-        // might fail.
-        //String value2 = (String) jcs.get( "abc" );
-        //assertEquals( "Couldn't put laterally, could be too much traffic in
-        // queue.", "testdata", value2 );
-
-    }
-
-    /**
-     * @param s string to print
-     */
-    public static void p( String s )
-    {
-        if ( isSysOut )
-        {
-            System.out.println( s );
-        }
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/LateralTCPDiscoveryListenerUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/LateralTCPDiscoveryListenerUnitTest.java
deleted file mode 100644
index 63b74a9..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/LateralTCPDiscoveryListenerUnitTest.java
+++ /dev/null
@@ -1,291 +0,0 @@
-package org.apache.commons.jcs.auxiliary.lateral.socket.tcp;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.auxiliary.lateral.LateralCache;
-import org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes;
-import org.apache.commons.jcs.auxiliary.lateral.LateralCacheNoWait;
-import org.apache.commons.jcs.auxiliary.lateral.LateralCacheNoWaitFacade;
-import org.apache.commons.jcs.auxiliary.lateral.behavior.ILateralCacheAttributes;
-import org.apache.commons.jcs.auxiliary.lateral.socket.tcp.behavior.ITCPLateralCacheAttributes;
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.engine.control.CompositeCacheManager;
-import org.apache.commons.jcs.engine.logging.MockCacheEventLogger;
-import org.apache.commons.jcs.utils.discovery.DiscoveredService;
-import org.apache.commons.jcs.utils.serialization.StandardSerializer;
-
-import java.util.ArrayList;
-
-/** Test for the listener that observers UDP discovery events. */
-public class LateralTCPDiscoveryListenerUnitTest
-    extends TestCase
-{
-    /** the listener */
-    private LateralTCPDiscoveryListener listener;
-
-    /** the cache factory */
-    private LateralTCPCacheFactory factory;
-
-    /** The cache manager. */
-    private CompositeCacheManager cacheMgr;
-
-    /** The event logger. */
-    protected MockCacheEventLogger cacheEventLogger;
-
-    /** The serializer. */
-    protected IElementSerializer elementSerializer;
-
-    /** Create the listener for testing */
-    @Override
-    protected void setUp() throws Exception
-    {
-        factory = new LateralTCPCacheFactory();
-        factory.initialize();
-
-        cacheMgr = CompositeCacheManager.getInstance();
-        cacheEventLogger = new MockCacheEventLogger();
-        elementSerializer = new StandardSerializer();
-
-        listener = new LateralTCPDiscoveryListener( factory.getName(), cacheMgr );
-    }
-
-    /**
-     * Add a no wait facade.
-     */
-    public void testAddNoWaitFacade_NotInList()
-    {
-        // SETUP
-        String cacheName = "testAddNoWaitFacade_NotInList";
-
-        @SuppressWarnings("unchecked")
-        LateralCacheNoWait<String, String>[] noWaits = new LateralCacheNoWait[0];
-        ILateralCacheAttributes cattr = new LateralCacheAttributes();
-        cattr.setCacheName( cacheName );
-
-        LateralCacheNoWaitFacade<String, String> facade = new LateralCacheNoWaitFacade<>( null, noWaits, cattr );
-
-        // DO WORK
-        listener.addNoWaitFacade( cacheName, facade );
-
-        // VERIFY
-        assertTrue( "Should have the facade.", listener.containsNoWaitFacade( cacheName ) );
-    }
-
-    /**
-     * Add a no wait to a known facade.
-     */
-    public void testAddNoWait_FacadeInList()
-    {
-        // SETUP
-        String cacheName = "testAddNoWaitFacade_FacadeInList";
-
-        @SuppressWarnings("unchecked")
-        LateralCacheNoWait<String, String>[] noWaits = new LateralCacheNoWait[0];
-        ILateralCacheAttributes cattr = new LateralCacheAttributes();
-        cattr.setCacheName( cacheName );
-
-        LateralCacheNoWaitFacade<String, String> facade = new LateralCacheNoWaitFacade<>( null, noWaits, cattr );
-        listener.addNoWaitFacade( cacheName, facade );
-
-        LateralCache<String, String> cache = new LateralCache<>( cattr );
-        LateralCacheNoWait<String, String> noWait = new LateralCacheNoWait<>( cache );
-
-        // DO WORK
-        boolean result = listener.addNoWait( noWait );
-
-        // VERIFY
-        assertTrue( "Should have added the no wait.", result );
-    }
-
-    /**
-     * Add a no wait from an unknown facade.
-     */
-    public void testAddNoWait_FacadeNotInList()
-    {
-        // SETUP
-        String cacheName = "testAddNoWaitFacade_FacadeInList";
-        ILateralCacheAttributes cattr = new LateralCacheAttributes();
-        cattr.setCacheName( cacheName );
-
-        LateralCache<String, String> cache = new LateralCache<>( cattr );
-        LateralCacheNoWait<String, String> noWait = new LateralCacheNoWait<>( cache );
-
-        // DO WORK
-        boolean result = listener.addNoWait( noWait );
-
-        // VERIFY
-        assertFalse( "Should not have added the no wait.", result );
-    }
-
-    /**
-     * Remove a no wait from an unknown facade.
-     */
-    public void testRemoveNoWait_FacadeNotInList()
-    {
-        // SETUP
-        String cacheName = "testRemoveNoWaitFacade_FacadeNotInList";
-        ILateralCacheAttributes cattr = new LateralCacheAttributes();
-        cattr.setCacheName( cacheName );
-
-        LateralCache<String, String> cache = new LateralCache<>( cattr );
-        LateralCacheNoWait<String, String> noWait = new LateralCacheNoWait<>( cache );
-
-        // DO WORK
-        boolean result = listener.removeNoWait( noWait );
-
-        // VERIFY
-        assertFalse( "Should not have removed the no wait.", result );
-    }
-
-    /**
-     * Remove a no wait from a known facade.
-     */
-    public void testRemoveNoWait_FacadeInList_NoWaitNot()
-    {
-        // SETUP
-        String cacheName = "testAddNoWaitFacade_FacadeInList";
-
-        @SuppressWarnings("unchecked")
-        LateralCacheNoWait<String, String>[] noWaits = new LateralCacheNoWait[0];
-        ILateralCacheAttributes cattr = new LateralCacheAttributes();
-        cattr.setCacheName( cacheName );
-
-        LateralCacheNoWaitFacade<String, String> facade = new LateralCacheNoWaitFacade<>( null, noWaits, cattr );
-        listener.addNoWaitFacade( cacheName, facade );
-
-        LateralCache<String, String> cache = new LateralCache<>( cattr );
-        LateralCacheNoWait<String, String> noWait = new LateralCacheNoWait<>( cache );
-
-        // DO WORK
-        boolean result = listener.removeNoWait( noWait );
-
-        // VERIFY
-        assertFalse( "Should not have removed the no wait.", result );
-    }
-
-    /**
-     * Remove a no wait from a known facade.
-     */
-    public void testRemoveNoWait_FacadeInList_NoWaitIs()
-    {
-        // SETUP
-        String cacheName = "testRemoveNoWaitFacade_FacadeInListNoWaitIs";
-
-        @SuppressWarnings("unchecked")
-        LateralCacheNoWait<String, String>[] noWaits = new LateralCacheNoWait[0];
-        ILateralCacheAttributes cattr = new LateralCacheAttributes();
-        cattr.setCacheName( cacheName );
-
-        LateralCacheNoWaitFacade<String, String> facade = new LateralCacheNoWaitFacade<>( null, noWaits, cattr );
-        listener.addNoWaitFacade( cacheName, facade );
-
-        LateralCache<String, String> cache = new LateralCache<>( cattr );
-        LateralCacheNoWait<String, String> noWait = new LateralCacheNoWait<>( cache );
-        listener.addNoWait( noWait );
-
-        // DO WORK
-        boolean result = listener.removeNoWait( noWait );
-
-        // VERIFY
-        assertTrue( "Should have removed the no wait.", result );
-    }
-
-    /**
-     * Add a no wait to a known facade.
-     */
-    public void testAddDiscoveredService_FacadeInList_NoWaitNot()
-    {
-        // SETUP
-        String cacheName = "testAddDiscoveredService_FacadeInList_NoWaitNot";
-
-        ArrayList<String> cacheNames = new ArrayList<>();
-        cacheNames.add( cacheName );
-
-        DiscoveredService service = new DiscoveredService();
-        service.setCacheNames( cacheNames );
-        service.setServiceAddress( "localhost" );
-        service.setServicePort( 9999 );
-
-        // since the no waits are compared by object equality, I have to do this
-        // TODO add an equals method to the noWait.  the problem if is figuring out what to compare.
-        ITCPLateralCacheAttributes lca = new TCPLateralCacheAttributes();
-        lca.setTransmissionType( LateralCacheAttributes.Type.TCP );
-        lca.setTcpServer( service.getServiceAddress() + ":" + service.getServicePort() );
-        lca.setCacheName(cacheName);
-        LateralCacheNoWait<String, String> noWait = factory.createCacheNoWait(lca, cacheEventLogger, elementSerializer);
-        // this is the normal process, the discovery service expects it there
-        cacheMgr.addAuxiliaryCache(factory.getName(), cacheName, noWait);
-
-        @SuppressWarnings("unchecked")
-        LateralCacheNoWait<String, String>[] noWaits = new LateralCacheNoWait[0];
-        ILateralCacheAttributes cattr = new LateralCacheAttributes();
-        cattr.setCacheName( cacheName );
-        LateralCacheNoWaitFacade<String, String> facade = new LateralCacheNoWaitFacade<>( null, noWaits, cattr );
-        listener.addNoWaitFacade( cacheName, facade );
-
-        // DO WORK
-        listener.addDiscoveredService( service );
-
-        // VERIFY
-        assertTrue( "Should have no wait.", listener.containsNoWait( cacheName, noWait ) );
-    }
-
-    /**
-     * Remove a no wait from a known facade.
-     */
-    public void testRemoveDiscoveredService_FacadeInList_NoWaitIs()
-    {
-        // SETUP
-        String cacheName = "testRemoveDiscoveredService_FacadeInList_NoWaitIs";
-
-        ArrayList<String> cacheNames = new ArrayList<>();
-        cacheNames.add( cacheName );
-
-        DiscoveredService service = new DiscoveredService();
-        service.setCacheNames( cacheNames );
-        service.setServiceAddress( "localhost" );
-        service.setServicePort( 9999 );
-
-        // since the no waits are compared by object equality, I have to do this
-        // TODO add an equals method to the noWait.  the problem if is figuring out what to compare.
-        ITCPLateralCacheAttributes lca = new TCPLateralCacheAttributes();
-        lca.setTransmissionType( LateralCacheAttributes.Type.TCP );
-        lca.setTcpServer( service.getServiceAddress() + ":" + service.getServicePort() );
-        lca.setCacheName(cacheName);
-        LateralCacheNoWait<String, String> noWait = factory.createCacheNoWait(lca, cacheEventLogger, elementSerializer);
-        // this is the normal process, the discovery service expects it there
-        cacheMgr.addAuxiliaryCache(factory.getName(), cacheName, noWait);
-
-        @SuppressWarnings("unchecked")
-        LateralCacheNoWait<String, String>[] noWaits = new LateralCacheNoWait[0];
-        ILateralCacheAttributes cattr = new LateralCacheAttributes();
-        cattr.setCacheName( cacheName );
-        LateralCacheNoWaitFacade<String, String> facade = new LateralCacheNoWaitFacade<>( null, noWaits, cattr );
-        listener.addNoWaitFacade( cacheName, facade );
-        listener.addDiscoveredService( service );
-
-        // DO WORK
-        listener.removeDiscoveredService( service );
-
-        // VERIFY
-        assertFalse( "Should not have no wait.", listener.containsNoWait( cacheName, noWait ) );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/LateralTCPFilterRemoveHashCodeUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/LateralTCPFilterRemoveHashCodeUnitTest.java
deleted file mode 100644
index 2084161..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/LateralTCPFilterRemoveHashCodeUnitTest.java
+++ /dev/null
@@ -1,192 +0,0 @@
-package org.apache.commons.jcs.auxiliary.lateral.socket.tcp;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-
-import java.io.Serializable;
-
-/**
- * @author Aaron Smuts
- */
-public class LateralTCPFilterRemoveHashCodeUnitTest
-    extends TestCase
-{
-    /** Does the test print to system out. */
-    private static boolean isSysOut = false;
-
-    /** The port the server will listen to. */
-    private final int serverPort = 2001;
-
-    /**
-     * Constructor for the TestDiskCache object.
-     *
-     * @param testName
-     */
-    public LateralTCPFilterRemoveHashCodeUnitTest( String testName )
-    {
-        super( testName );
-    }
-
-    /**
-     * Test setup
-     */
-    @Override
-    public void setUp()
-    {
-        System.setProperty( "jcs.auxiliary.LTCP.attributes.TcpServers", "localhost:" + serverPort );
-        JCS.setConfigFilename( "/TestTCPLateralRemoveFilter.ccf" );
-    }
-
-    /**
-     *
-     * @throws Exception
-     */
-    public void test()
-        throws Exception
-    {
-        this.runTestForRegion( "region1", 200, 1 );
-    }
-
-    /**
-     * This tests issues tons of puts. It also check to see that a key that was
-     * put in was removed by the clients remove command.
-     *
-     * @param region
-     *            Name of the region to access
-     * @param numOps
-     * @param testNum
-     *
-     * @throws Exception
-     *                If an error occurs
-     */
-    public void runTestForRegion( String region, int numOps, int testNum )
-        throws Exception
-    {
-        CacheAccess<String, Serializable> cache = JCS.getInstance( region );
-
-        Thread.sleep( 100 );
-
-        TCPLateralCacheAttributes lattr2 = new TCPLateralCacheAttributes();
-        lattr2.setTcpListenerPort( 1102 );
-        lattr2.setTransmissionTypeName( "TCP" );
-        lattr2.setTcpServer( "localhost:" + serverPort );
-        lattr2.setIssueRemoveOnPut( true );
-        // should still try to remove
-        lattr2.setAllowPut( false );
-
-        // this service will put and remove using the lateral to
-        // the cache instance above
-        // the cache thinks it is different since the listenerid is different
-        LateralTCPService<String, Serializable> service = new LateralTCPService<>( lattr2 );
-        service.setListenerId( 123456 );
-
-        String keyToBeRemovedOnPut = "test1";
-
-        String keyToNotBeRemovedOnPut = "test2";
-
-        Serializable dataToPassHashCodeCompare = new Serializable()
-        {
-            private static final long serialVersionUID = 1L;
-
-            @Override
-            public int hashCode()
-            {
-                return 1;
-            }
-        };
-        //String dataToPassHashCodeCompare = "this should be the same and not
-        // get removed.";
-        //p( "dataToPassHashCodeCompare hashcode = " + +
-        // dataToPassHashCodeCompare.hashCode() );
-
-        cache.put( keyToBeRemovedOnPut, "this should get removed." );
-        ICacheElement<String, Serializable> element1 = new CacheElement<>( region, keyToBeRemovedOnPut, region
-            + ":data-this shouldn't get there" );
-        service.update( element1 );
-
-        cache.put( keyToNotBeRemovedOnPut, dataToPassHashCodeCompare );
-        ICacheElement<String, Serializable> element2 = new CacheElement<>( region, keyToNotBeRemovedOnPut, dataToPassHashCodeCompare );
-        service.update( element2 );
-
-        /*
-         * try { for ( int i = 1; i < numOps; i++ ) { Random ran = new Random( i );
-         * int n = ran.nextInt( 4 ); int kn = ran.nextInt( range ); String key =
-         * "key" + kn;
-         *
-         * ICacheElement<String, String> element = new CacheElement( region, key, region +
-         * ":data" + i + " junk asdfffffffadfasdfasf " + kn + ":" + n );
-         * service.update( element ); if ( show ) { p( "put " + key ); }
-         *
-         * if ( i % 100 == 0 ) { System.out.println( cache.getStats() ); }
-         *  } p( "Finished cycle of " + numOps ); } catch ( Exception e ) { p(
-         * e.toString() ); e.printStackTrace( System.out ); throw e; }
-         */
-
-        CacheAccess<String, String> jcs = JCS.getInstance( region );
-        String key = "testKey" + testNum;
-        String data = "testData" + testNum;
-        jcs.put( key, data );
-        String value = jcs.get( key );
-        assertEquals( "Couldn't put normally.", data, value );
-
-        // make sure the items we can find are in the correct region.
-        for ( int i = 1; i < numOps; i++ )
-        {
-            String keyL = "key" + i;
-            String dataL = jcs.get( keyL );
-            if ( dataL != null )
-            {
-                assertTrue( "Incorrect region detected.", dataL.startsWith( region ) );
-            }
-
-        }
-
-        Thread.sleep( 200 );
-
-        Object testObj1 = cache.get( keyToBeRemovedOnPut );
-        p( "test object1 = " + testObj1 );
-        assertNull( "The test object should have been remvoed by a put.", testObj1 );
-
-        Object testObj2 = cache.get( keyToNotBeRemovedOnPut );
-        p( "test object2 = " + testObj2 + " hashCode = " );
-        if ( testObj2 != null )
-        {
-            p( "test2 hashcode = " + +testObj2.hashCode() );
-        }
-        assertNotNull( "This should not have been removed, since the hascode were the same.", testObj2 );
-
-    }
-
-    /**
-     * @param s String to print
-     */
-    public static void p( String s )
-    {
-        if ( isSysOut )
-        {
-            System.out.println( s );
-        }
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/LateralTCPIssueRemoveOnPutUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/LateralTCPIssueRemoveOnPutUnitTest.java
deleted file mode 100644
index d2cd13e..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/LateralTCPIssueRemoveOnPutUnitTest.java
+++ /dev/null
@@ -1,225 +0,0 @@
-package org.apache.commons.jcs.auxiliary.lateral.socket.tcp;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-
-import java.util.Random;
-
-/**
- * Tests the issue remove on put fuctionality.
- * @author asmuts
- */
-public class LateralTCPIssueRemoveOnPutUnitTest
-    extends TestCase
-{
-    /** Should log data go to system out. */
-    private static boolean isSysOut = false;
-
-    /** The port the server will listen to. */
-    private final int serverPort = 1118;
-
-    /**
-     * Constructor for the TestDiskCache object.
-     * <p>
-     * @param testName
-     */
-    public LateralTCPIssueRemoveOnPutUnitTest( String testName )
-    {
-        super( testName );
-    }
-
-    /**
-     * Test setup
-     */
-    @Override
-    public void setUp()
-    {
-        System.setProperty( "jcs.auxiliary.LTCP.attributes.TcpServers", "localhost:" + serverPort );
-
-        JCS.setConfigFilename( "/TestTCPLateralIssueRemoveCache.ccf" );
-    }
-
-    /**
-     * @throws Exception
-     */
-    public void testPutLocalPutRemoteGetBusyVerifyRemoved()
-        throws Exception
-    {
-        this.runTestForRegion( "region1", 1, 200, 1 );
-    }
-
-    /**
-     * Verify that a standard put works. Get the cache configured from a file. Create a tcp service
-     * to talk to that cache. Put via the service. Verify that the cache got the data.
-     * <p>
-     * @throws Exception
-     */
-    public void testStandardPut()
-        throws Exception
-    {
-        String region = "region1";
-
-        CacheAccess<String, String> cache = JCS.getInstance( region );
-
-        Thread.sleep( 100 );
-
-        TCPLateralCacheAttributes lattr2 = new TCPLateralCacheAttributes();
-        lattr2.setTcpListenerPort( 1102 );
-        lattr2.setTransmissionTypeName( "TCP" );
-        lattr2.setTcpServer( "localhost:" + serverPort );
-        lattr2.setIssueRemoveOnPut( false );
-        // should still try to remove
-        // lattr2.setAllowPut( false );
-
-        // Using the lateral, this service will put to and remove from
-        // the cache instance above.
-        // The cache thinks it is different since the listenerid is different
-        LateralTCPService<String, String> service = new LateralTCPService<>( lattr2 );
-        service.setListenerId( 123456 );
-
-        String keyToBeRemovedOnPut = "test1_notremoved";
-
-        ICacheElement<String, String> element1 = new CacheElement<>( region, keyToBeRemovedOnPut, region
-            + ":data-this shouldn't get removed, it should get to the cache." );
-        service.update( element1 );
-
-        Thread.sleep( 1000 );
-
-        Object testObj = cache.get( keyToBeRemovedOnPut );
-        p( "testStandardPut, test object = " + testObj );
-        assertNotNull( "The test object should not have been removed by a put.", testObj );
-    }
-
-    /**
-     * This tests issues tons of puts. It also check to see that a key that was put in was removed
-     * by the clients remove command.
-     * <p>
-     * @param region Name of the region to access
-     * @param range
-     * @param numOps
-     * @param testNum
-     * @throws Exception If an error occurs
-     */
-    public void runTestForRegion( String region, int range, int numOps, int testNum )
-        throws Exception
-    {
-
-        boolean show = false;
-
-        CacheAccess<String, String> cache = JCS.getInstance( region );
-
-        Thread.sleep( 100 );
-
-        TCPLateralCacheAttributes lattr2 = new TCPLateralCacheAttributes();
-        lattr2.setTcpListenerPort( 1102 );
-        lattr2.setTransmissionTypeName( "TCP" );
-        lattr2.setTcpServer( "localhost:" + serverPort );
-        lattr2.setIssueRemoveOnPut( true );
-        // should still try to remove
-        lattr2.setAllowPut( false );
-
-        // Using the lateral, this service will put to and remove from
-        // the cache instance above.
-        // The cache thinks it is different since the listenerid is different
-        LateralTCPService<String, String> service = new LateralTCPService<>( lattr2 );
-        service.setListenerId( 123456 );
-
-        String keyToBeRemovedOnPut = "test1";
-        cache.put( keyToBeRemovedOnPut, "this should get removed." );
-
-        ICacheElement<String, String> element1 = new CacheElement<>( region, keyToBeRemovedOnPut, region
-            + ":data-this shouldn't get there" );
-        service.update( element1 );
-
-        try
-        {
-            for ( int i = 1; i < numOps; i++ )
-            {
-                Random ran = new Random( i );
-                int n = ran.nextInt( 4 );
-                int kn = ran.nextInt( range );
-                String key = "key" + kn;
-
-                ICacheElement<String, String> element = new CacheElement<>( region, key, region + ":data" + i
-                    + " junk asdfffffffadfasdfasf " + kn + ":" + n );
-                service.update( element );
-                if ( show )
-                {
-                    p( "put " + key );
-                }
-
-                if (show && i % 100 == 0 )
-                {
-                    System.out.println( cache.getStats() );
-                }
-
-            }
-            p( "Finished cycle of " + numOps );
-        }
-        catch ( Exception e )
-        {
-            p( e.toString() );
-            e.printStackTrace( System.out );
-            throw e;
-        }
-
-        CacheAccess<String, String> jcs = JCS.getInstance( region );
-        String key = "testKey" + testNum;
-        String data = "testData" + testNum;
-        jcs.put( key, data );
-        String value = jcs.get( key );
-        assertEquals( "Couldn't put normally.", data, value );
-
-        // make sure the items we can find are in the correct region.
-        for ( int i = 1; i < numOps; i++ )
-        {
-            String keyL = "key" + i;
-            String dataL = jcs.get( keyL );
-            if ( dataL != null )
-            {
-                assertTrue( "Incorrect region detected.", dataL.startsWith( region ) );
-            }
-
-        }
-
-        Thread.sleep( 200 );
-
-        Object testObj = cache.get( keyToBeRemovedOnPut );
-        p( "runTestForRegion, test object = " + testObj );
-        assertNull( "The test object should have been removed by a put.", testObj );
-
-    }
-
-    /**
-     * @param s String to be printed
-     */
-    public static void p( String s )
-    {
-        if ( isSysOut )
-        {
-            System.out.println( s );
-        }
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/LateralTCPNoDeadLockConcurrentTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/LateralTCPNoDeadLockConcurrentTest.java
deleted file mode 100644
index 8019f85..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/LateralTCPNoDeadLockConcurrentTest.java
+++ /dev/null
@@ -1,146 +0,0 @@
-package org.apache.commons.jcs.auxiliary.lateral.socket.tcp;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.extensions.ActiveTestSuite;
-import junit.framework.Test;
-import junit.framework.TestCase;
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.engine.control.CompositeCacheManager;
-
-/**
- * Test which exercises the tcp lateral cache. Runs two threads against the
- * same region and two against other regions.
- */
-public class LateralTCPNoDeadLockConcurrentTest
-    extends TestCase
-{
-    /**
-     * Constructor for the TestDiskCache object.
-     *
-     * @param testName
-     */
-    public LateralTCPNoDeadLockConcurrentTest( String testName )
-    {
-        super( testName );
-    }
-
-    /**
-     * Main method passes this test to the text test runner.
-     *
-     * @param args
-     */
-    public static void main( String args[] )
-    {
-        String[] testCaseName = { LateralTCPNoDeadLockConcurrentTest.class.getName() };
-        junit.textui.TestRunner.main( testCaseName );
-    }
-
-    /**
-     * A unit test suite for JUnit
-     *
-     * @return The test suite
-     */
-    public static Test suite()
-    {
-
-        System.setProperty( "jcs.auxiliary.LTCP.attributes.PutOnlyMode", "false" );
-
-        ActiveTestSuite suite = new ActiveTestSuite();
-
-        suite.addTest( new LateralTCPConcurrentRandomTestUtil( "testLateralTCPCache1" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "region1", 1, 200, 1 );
-            }
-        } );
-
-        suite.addTest( new LateralTCPConcurrentRandomTestUtil( "testLateralTCPCache2" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "region2", 10000, 12000, 2 );
-            }
-        } );
-
-        suite.addTest( new LateralTCPConcurrentRandomTestUtil( "testLateralTCPCache3" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "region3", 10000, 12000, 3 );
-            }
-        } );
-
-        suite.addTest( new LateralTCPConcurrentRandomTestUtil( "testLateralTCPCache4" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "region3", 10000, 13000, 4 );
-            }
-        } );
-
-        suite.addTest( new LateralTCPConcurrentRandomTestUtil( "testLateralTCPCache5" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "region4", 10000, 11000, 5 );
-            }
-        } );
-
-        return suite;
-    }
-
-    /**
-     * Test setup
-     */
-    @Override
-    public void setUp()
-    {
-        JCS.setConfigFilename( "/TestTCPLateralCacheConcurrent.ccf" );
-    }
-
-    /**
-     * Test tearDown. Dispose of the cache.
-     */
-    @Override
-    public void tearDown()
-    {
-        try
-        {
-            CompositeCacheManager cacheMgr = CompositeCacheManager.getInstance();
-            cacheMgr.shutDown();
-        }
-        catch ( Exception e )
-        {
-            e.printStackTrace();
-        }
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/TestTCPLateralUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/TestTCPLateralUnitTest.java
deleted file mode 100644
index 631ff0a..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/lateral/socket/tcp/TestTCPLateralUnitTest.java
+++ /dev/null
@@ -1,368 +0,0 @@
-package org.apache.commons.jcs.auxiliary.lateral.socket.tcp;
-
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes;
-import org.apache.commons.jcs.auxiliary.lateral.LateralCommand;
-import org.apache.commons.jcs.auxiliary.lateral.LateralElementDescriptor;
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
-import org.apache.commons.jcs.engine.control.CompositeCache;
-import org.apache.commons.jcs.engine.control.CompositeCacheManager;
-import org.apache.commons.jcs.engine.control.MockCompositeCacheManager;
-import org.apache.commons.jcs.engine.control.group.GroupAttrName;
-import org.apache.commons.jcs.engine.control.group.GroupId;
-import org.apache.commons.jcs.utils.timing.SleepUtil;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-
-/**
- * Basic unit tests for the sending and receiving portions of the lateral cache.
- * <p>
- * @author Aaron Smuts
- */
-public class TestTCPLateralUnitTest
-    extends TestCase
-{
-    /**
-     * Test setup
-     */
-    @Override
-    public void setUp()
-    {
-        JCS.setConfigFilename( "/TestTCPLateralCache.ccf" );
-    }
-
-    /**
-     * Make sure we can send a bunch to the listener. This would be better if we could plugin a Mock
-     * CacheManger. The listener will instantiate it on its own. We have to configure one before
-     * that.
-     * <p>
-     * @throws Exception
-     */
-    public void testSimpleSend()
-        throws Exception
-    {
-        // SETUP
-        // force initialization
-        JCS.getInstance( "test" );
-
-        TCPLateralCacheAttributes lac = new TCPLateralCacheAttributes();
-        lac.setTransmissionType( LateralCacheAttributes.Type.TCP );
-        lac.setTcpServer( "localhost" + ":" + 8111 );
-        lac.setTcpListenerPort( 8111 );
-
-        ICompositeCacheManager cacheMgr = CompositeCacheManager.getInstance();
-
-        // start the listener
-        LateralTCPListener<String, String> listener = LateralTCPListener.getInstance( lac, cacheMgr );
-
-        // send to the listener
-        LateralTCPSender lur = new LateralTCPSender( lac );
-
-        // DO WORK
-        int numMes = 10;
-        for ( int i = 0; i < numMes; i++ )
-        {
-            String message = "adsfasasfasfasdasf";
-            CacheElement<String, String> ce = new CacheElement<>( "test", "test", message );
-            LateralElementDescriptor<String, String> led = new LateralElementDescriptor<>( ce );
-            led.command = LateralCommand.UPDATE;
-            led.requesterId = 1;
-            lur.send( led );
-        }
-
-        SleepUtil.sleepAtLeast( numMes * 3 );
-
-        // VERIFY
-        assertEquals( "Should have received " + numMes + " by now.", numMes, listener.getPutCnt() );
-    }
-
-    /**
-     * @throws Exception
-     */
-    public void testReceive()
-        throws Exception
-    {
-        // VERIFY
-        TCPLateralCacheAttributes lattr = new TCPLateralCacheAttributes();
-        lattr.setTcpListenerPort( 1101 );
-        lattr.setTransmissionTypeName( "TCP" );
-        MockCompositeCacheManager cacheMgr = new MockCompositeCacheManager();
-//        System.out.println( "mock cache = " + cacheMgr.getCache( "test" ) );
-
-        LateralTCPListener.getInstance( lattr, cacheMgr );
-
-        TCPLateralCacheAttributes lattr2 = new TCPLateralCacheAttributes();
-        lattr2.setTcpListenerPort( 1102 );
-        lattr2.setTransmissionTypeName( "TCP" );
-        lattr2.setTcpServer( "localhost:1101" );
-
-        LateralTCPService<String, String> service = new LateralTCPService<>( lattr2 );
-        service.setListenerId( 123456 );
-
-        // DO WORK
-        int cnt = 100;
-        for ( int i = 0; i < cnt; i++ )
-        {
-            ICacheElement<String, String> element = new CacheElement<>( "test", "key" + i, "value1" );
-            service.update( element );
-        }
-
-        SleepUtil.sleepAtLeast( 1000 );
-
-        // VERIFY
-        assertEquals( "Didn't get the correct number", cnt, cacheMgr.getCache().getUpdateCount() );
-    }
-
-    /**
-     * Send objects with the same key but different values.
-     * <p>
-     * @throws Exception
-     */
-    public void testSameKeyDifferentObject()
-        throws Exception
-    {
-        // SETUP
-        // setup a listener
-        TCPLateralCacheAttributes lattr = new TCPLateralCacheAttributes();
-        lattr.setTcpListenerPort( 1103 );
-        MockCompositeCacheManager cacheMgr = new MockCompositeCacheManager();
-        CompositeCache<String, String> cache = cacheMgr.getCache( "test" );
-//        System.out.println( "mock cache = " + cache );
-
-        // get the listener started
-        // give it our mock cache manager
-        //LateralTCPListener listener = (LateralTCPListener)
-        LateralTCPListener.getInstance( lattr, cacheMgr );
-
-        // setup a service to talk to the listener started above.
-        TCPLateralCacheAttributes lattr2 = new TCPLateralCacheAttributes();
-        lattr2.setTcpListenerPort( 1104 );
-        lattr2.setTcpServer( "localhost:1103" );
-
-        LateralTCPService<String, String> service = new LateralTCPService<>( lattr2 );
-        service.setListenerId( 123456 );
-
-        // DO WORK
-        ICacheElement<String, String> element = new CacheElement<>( "test", "key", "value1" );
-        service.update( element );
-
-        SleepUtil.sleepAtLeast( 300 );
-
-        ICacheElement<String, String> element2 = new CacheElement<>( "test", "key", "value2" );
-        service.update( element2 );
-
-        SleepUtil.sleepAtLeast( 1000 );
-
-        // VERIFY
-        ICacheElement<String, String> cacheElement = cache.get( "key" );
-        assertEquals( "Didn't get the correct object "+ cacheElement, element2.getVal(), cacheElement.getVal() );
-    }
-
-    /**
-     * Send objects with the same key but different values.
-     * <p>
-     * @throws Exception
-     */
-    public void testSameKeyObjectDifferentValueObject()
-        throws Exception
-    {
-        TCPLateralCacheAttributes lattr = new TCPLateralCacheAttributes();
-        lattr.setTcpListenerPort( 1105 );
-        lattr.setTransmissionTypeName( "TCP" );
-        MockCompositeCacheManager cacheMgr = new MockCompositeCacheManager();
-        CompositeCache<String, String> cache = cacheMgr.getCache( "test" );
-//        System.out.println( "mock cache = " + cache );
-
-        // get the listener started
-        // give it our mock cache manager
-        //LateralTCPListener listener = (LateralTCPListener)
-        LateralTCPListener.getInstance( lattr, cacheMgr );
-
-        TCPLateralCacheAttributes lattr2 = new TCPLateralCacheAttributes();
-        lattr2.setTcpListenerPort( 1106 );
-        lattr2.setTransmissionTypeName( "TCP" );
-        lattr2.setTcpServer( "localhost:1105" );
-
-        LateralTCPService<String, String> service = new LateralTCPService<>( lattr2 );
-        service.setListenerId( 123456 );
-
-        // DO WORK
-        String key = "key";
-        ICacheElement<String, String> element = new CacheElement<>( "test", key, "value1" );
-        service.update( element );
-
-        SleepUtil.sleepAtLeast( 300 );
-
-        ICacheElement<String, String> element2 = new CacheElement<>( "test", key, "value2" );
-        service.update( element2 );
-
-        SleepUtil.sleepAtLeast( 1000 );
-
-        // VERIFY
-        ICacheElement<String, String> cacheElement = cache.get( "key" );
-        assertEquals( "Didn't get the correct object: " + cacheElement , element2.getVal(), cacheElement.getVal() );
-    }
-
-    /**
-     * Create a listener. Add an element to the listeners cache. Setup a service. Try to get from
-     * the service.
-     * <p>
-     * @throws Exception
-     */
-    public void testGet_SendAndReceived()
-        throws Exception
-    {
-        // SETUP
-        // setup a listener
-        TCPLateralCacheAttributes lattr = new TCPLateralCacheAttributes();
-        lattr.setTcpListenerPort( 1107 );
-        MockCompositeCacheManager cacheMgr = new MockCompositeCacheManager();
-        CompositeCache<String, String> cache = cacheMgr.getCache( "test" );
-//        System.out.println( "mock cache = " + cache );
-
-        // get the listener started
-        // give it our mock cache manager
-        LateralTCPListener.getInstance( lattr, cacheMgr );
-
-        // add the item to the listeners cache
-        ICacheElement<String, String> element = new CacheElement<>( "test", "key", "value1" );
-        cache.update( element );
-
-        // setup a service to talk to the listener started above.
-        TCPLateralCacheAttributes lattr2 = new TCPLateralCacheAttributes();
-        lattr2.setTcpListenerPort( 1108 );
-        lattr2.setTcpServer( "localhost:1107" );
-
-        LateralTCPService<String, String> service = new LateralTCPService<>( lattr2 );
-        service.setListenerId( 123456 );
-
-        SleepUtil.sleepAtLeast( 300 );
-
-        // DO WORK
-        ICacheElement<String, String> result = service.get( "test", "key" );
-
-        // VERIFY
-        assertNotNull( "Result should not be null.", result );
-        assertEquals( "Didn't get the correct object", element.getVal(), result.getVal() );
-    }
-
-    /**
-     * Create a listener. Add an element to the listeners cache. Setup a service. Try to get keys from
-     * the service.
-     * <p>
-     * @throws Exception
-     */
-    public void testGetGroupKeys_SendAndReceived()  throws Exception
-    {
-        // SETUP
-        // setup a listener
-        TCPLateralCacheAttributes lattr = new TCPLateralCacheAttributes();
-        lattr.setTcpListenerPort( 1150 );
-        MockCompositeCacheManager cacheMgr = new MockCompositeCacheManager();
-        CompositeCache<GroupAttrName<String>, String> cache = cacheMgr.getCache( "test" );
-//        System.out.println( "mock cache = " + cache );
-
-        // get the listener started
-        // give it our mock cache manager
-        LateralTCPListener.getInstance( lattr, cacheMgr );
-
-        // add the item to the listeners cache
-        GroupAttrName<String> groupKey = new GroupAttrName<>(new GroupId("test", "group"), "key");
-        ICacheElement<GroupAttrName<String>, String> element =
-            new CacheElement<>( "test", groupKey, "value1" );
-        cache.update( element );
-
-        // setup a service to talk to the listener started above.
-        TCPLateralCacheAttributes lattr2 = new TCPLateralCacheAttributes();
-        lattr2.setTcpListenerPort( 1151 );
-        lattr2.setTcpServer( "localhost:1150" );
-
-        LateralTCPService<GroupAttrName<String>, String> service =
-            new LateralTCPService<>( lattr2 );
-        service.setListenerId( 123459 );
-
-        SleepUtil.sleepAtLeast( 500 );
-
-        // DO WORK
-        Set<GroupAttrName<String>> result = service.getKeySet("test");
-
-       // SleepUtil.sleepAtLeast( 5000000 );
-
-        // VERIFY
-        assertNotNull( "Result should not be null.", result );
-        assertEquals( "Didn't get the correct object", "key", result.iterator().next().attrName );
-    }
-
-    /**
-     * Create a listener. Add an element to the listeners cache. Setup a service. Try to get from
-     * the service.
-     * <p>
-     * @throws Exception
-     */
-    public void testGetMatching_WithData()
-        throws Exception
-    {
-        // SETUP
-        // setup a listener
-        TCPLateralCacheAttributes lattr = new TCPLateralCacheAttributes();
-        lattr.setTcpListenerPort( 1108 );
-        MockCompositeCacheManager cacheMgr = new MockCompositeCacheManager();
-        CompositeCache<String, Integer> cache = cacheMgr.getCache( "test" );
-//        System.out.println( "mock cache = " + cache );
-
-        // get the listener started
-        // give it our mock cache manager
-        LateralTCPListener.getInstance( lattr, cacheMgr );
-
-        String keyprefix1 = "MyPrefix1";
-        int numToInsertPrefix1 = 10;
-        // insert with prefix1
-        for ( int i = 0; i < numToInsertPrefix1; i++ )
-        {
-            // add the item to the listeners cache
-            ICacheElement<String, Integer> element = new CacheElement<>( "test", keyprefix1 + String.valueOf( i ), Integer.valueOf( i ) );
-            cache.update( element );
-        }
-
-        // setup a service to talk to the listener started above.
-        TCPLateralCacheAttributes lattr2 = new TCPLateralCacheAttributes();
-        lattr2.setTcpListenerPort( 1108 );
-        lattr2.setTcpServer( "localhost:1108" );
-
-        LateralTCPService<String, Integer> service = new LateralTCPService<>( lattr2 );
-        service.setListenerId( 123456 );
-
-        SleepUtil.sleepAtLeast( 300 );
-
-        // DO WORK
-        Map<String, ICacheElement<String, Integer>> result = service.getMatching( "test", keyprefix1 + ".+" );
-
-        // VERIFY
-        assertNotNull( "Result should not be null.", result );
-        assertEquals( "Wrong number returned 1:", numToInsertPrefix1, result.size() );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/MockRemoteCacheClient.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/MockRemoteCacheClient.java
deleted file mode 100644
index 9d78664..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/MockRemoteCacheClient.java
+++ /dev/null
@@ -1,261 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.commons.jcs.auxiliary.AbstractAuxiliaryCache;
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes;
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheClient;
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheListener;
-import org.apache.commons.jcs.engine.CacheStatus;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheServiceNonLocal;
-import org.apache.commons.jcs.engine.stats.behavior.IStats;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * Used for testing the no wait.
- * <p>
- * @author Aaron Smuts
- */
-public class MockRemoteCacheClient<K, V>
-    extends AbstractAuxiliaryCache<K, V>
-    implements IRemoteCacheClient<K, V>
-{
-    /** log instance */
-    private static final Log log = LogManager.getLog( MockRemoteCacheClient.class );
-
-    /** List of ICacheElement&lt;K, V&gt; objects passed into update. */
-    public List<ICacheElement<K, V>> updateList = new LinkedList<>();
-
-    /** List of key objects passed into remove. */
-    public List<K> removeList = new LinkedList<>();
-
-    /** status to return. */
-    public CacheStatus status = CacheStatus.ALIVE;
-
-    /** Can setup values to return from get. values must be ICacheElement&lt;K, V&gt; */
-    public Map<K, ICacheElement<K, V>> getSetupMap = new HashMap<>();
-
-    /** Can setup values to return from get. values must be Map&lt;K, ICacheElement&lt;K, V&gt;&gt; */
-    public Map<Set<K>, Map<K, ICacheElement<K, V>>> getMultipleSetupMap =
-        new HashMap<>();
-
-    /** The last service passed to fixCache */
-    public ICacheServiceNonLocal<K, V> fixed;
-
-    /** Attributes. */
-    public RemoteCacheAttributes attributes = new RemoteCacheAttributes();
-
-    /**
-     * Stores the last argument as fixed.
-     */
-    @Override
-    @SuppressWarnings("unchecked") // Don't know how to do this properly
-    public void fixCache( ICacheServiceNonLocal<?, ?> remote )
-    {
-        fixed = (ICacheServiceNonLocal<K, V>)remote;
-    }
-
-    /**
-     * @return long
-     */
-    @Override
-    public long getListenerId()
-    {
-        return 0;
-    }
-
-    /**
-     * @return null
-     */
-    @Override
-    public IRemoteCacheListener<K, V> getListener()
-    {
-        return null;
-    }
-
-    /**
-     * Adds the argument to the updatedList.
-     */
-    @Override
-    public void update( ICacheElement<K, V> ce )
-    {
-        updateList.add( ce );
-    }
-
-    /**
-     * Looks in the getSetupMap for a value.
-     */
-    @Override
-    public ICacheElement<K, V> get( K key )
-    {
-        log.info( "get [" + key + "]" );
-        return getSetupMap.get( key );
-    }
-
-    /**
-     * Gets multiple items from the cache based on the given set of keys.
-     * <p>
-     * @param keys
-     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
-     *         data in cache for any of these keys
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMultiple(Set<K> keys)
-    {
-        log.info( "get [" + keys + "]" );
-        return getMultipleSetupMap.get( keys );
-    }
-
-    /**
-     * Adds the key to the remove list.
-     */
-    @Override
-    public boolean remove( K key )
-    {
-        removeList.add( key );
-        return false;
-    }
-
-    /**
-     * Removes all cached items from the cache.
-     */
-    @Override
-    public void removeAll()
-    {
-        // do nothing
-    }
-
-    /**
-     * Prepares for shutdown.
-     */
-    @Override
-    public void dispose()
-    {
-        // do nothing
-    }
-
-    /**
-     * Returns the current cache size in number of elements.
-     * <p>
-     * @return number of elements
-     */
-    @Override
-    public int getSize()
-    {
-        return 0;
-    }
-
-    /**
-     * Returns the status setup variable.
-     */
-    @Override
-    public CacheStatus getStatus()
-    {
-        return status;
-    }
-
-    /**
-     * Returns the cache name.
-     * <p>
-     * @return usually the region name.
-     */
-    @Override
-    public String getCacheName()
-    {
-        return null;
-    }
-
-    /**
-     * @return null
-     */
-    @Override
-    public Set<K> getKeySet( )
-    {
-        return null;
-    }
-
-    /**
-     * @return null
-     */
-    @Override
-    public IStats getStatistics()
-    {
-        return null;
-    }
-
-    /**
-     * Returns the setup attributes. By default they are not null.
-     */
-    @Override
-    public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
-    {
-        return attributes;
-    }
-
-    /**
-     * Returns the cache stats.
-     * <p>
-     * @return String of important historical information.
-     */
-    @Override
-    public String getStats()
-    {
-        return null;
-    }
-
-    /** @return 0 */
-    @Override
-    public CacheType getCacheType()
-    {
-        return CacheType.REMOTE_CACHE;
-    }
-
-    /**
-     * @param pattern
-     * @return Map
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMatching(String pattern)
-        throws IOException
-    {
-        return new HashMap<>();
-    }
-
-    /**
-     * Nothing important
-     * <p>
-     * @return null
-     */
-    @Override
-    public String getEventLoggingExtraInfo()
-    {
-        return null;
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/MockRemoteCacheListener.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/MockRemoteCacheListener.java
deleted file mode 100644
index 4295ec6..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/MockRemoteCacheListener.java
+++ /dev/null
@@ -1,168 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheListener;
-import org.apache.commons.jcs.auxiliary.remote.server.behavior.RemoteType;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-
-/**
- * For testing.
- * <p>
- * @author Aaron Smuts
- */
-public class MockRemoteCacheListener<K, V>
-    implements IRemoteCacheListener<K, V>
-{
-    /** Setup the listener id that this will return. */
-    private long listenerId;
-
-    /** Setup the listener ip that this will return. */
-    public String localAddress;
-
-    /** Number of times handlePut was called. */
-    public int putCount;
-
-    /** List of ICacheElements passed to handlePut. */
-    public List<ICacheElement<K, V>> putItems = new LinkedList<>();
-
-    /** List of Serializable objects passed to handleRemove. */
-    public List<K> removedKeys = new LinkedList<>();
-
-    /** Number of times handleRemote was called. */
-    public int removeCount;
-
-    /** The type of remote listener */
-    public RemoteType remoteType = RemoteType.LOCAL;
-
-    /**
-     * @throws IOException
-     */
-    @Override
-    public void dispose()
-        throws IOException
-    {
-        // TODO Auto-generated method stub
-    }
-
-    /**
-     * returns the listener id, which can be setup.
-     * @return listenerId
-     * @throws IOException
-     */
-    @Override
-    public long getListenerId()
-        throws IOException
-    {
-        return listenerId;
-    }
-
-    /**
-     * @return localAddress
-     * @throws IOException
-     */
-    @Override
-    public String getLocalHostAddress()
-        throws IOException
-    {
-        return localAddress;
-    }
-
-    /**
-     * Return the setup remoteType.
-     * @return remoteType
-     * @throws IOException
-     */
-    @Override
-    public RemoteType getRemoteType()
-        throws IOException
-    {
-        return remoteType;
-    }
-
-    /**
-     * Allows you to setup the listener id.
-     * <p>
-     * @param id
-     * @throws IOException
-     */
-    @Override
-    public void setListenerId( long id )
-        throws IOException
-    {
-        listenerId = id;
-    }
-
-    /**
-     * @param cacheName
-     * @throws IOException
-     */
-    @Override
-    public void handleDispose( String cacheName )
-        throws IOException
-    {
-        // TODO Auto-generated method stub
-
-    }
-
-    /**
-     * This increments the put count and adds the item to the putItem list.
-     * <p>
-     * @param item
-     * @throws IOException
-     */
-    @Override
-    public void handlePut( ICacheElement<K, V> item )
-        throws IOException
-    {
-        putCount++;
-        this.putItems.add( item );
-    }
-
-    /**
-     * Increments the remove count and adds the key to the removedKeys list.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @throws IOException
-     */
-    @Override
-    public void handleRemove( String cacheName, K key )
-        throws IOException
-    {
-        removeCount++;
-        removedKeys.add( key );
-    }
-
-    /**
-     * @param cacheName
-     * @throws IOException
-     */
-    @Override
-    public void handleRemoveAll( String cacheName )
-        throws IOException
-    {
-        // TODO Auto-generated method stub
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/MockRemoteCacheService.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/MockRemoteCacheService.java
deleted file mode 100644
index b004220..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/MockRemoteCacheService.java
+++ /dev/null
@@ -1,241 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheServiceNonLocal;
-
-/**
- * This is a mock impl of the remote cache service.
- */
-public class MockRemoteCacheService<K, V>
-    implements ICacheServiceNonLocal<K, V>
-{
-    /** The key last passed to get */
-    public K lastGetKey;
-
-    /** The pattern last passed to get */
-    public String lastGetMatchingPattern;
-
-    /** The keya last passed to getMatching */
-    public Set<K> lastGetMultipleKeys;
-
-    /** The object that was last passed to update. */
-    public ICacheElement<K, V> lastUpdate;
-
-    /** List of updates. */
-    public List<ICacheElement<K, V>> updateRequestList = new ArrayList<>();
-
-    /** List of request ids. */
-    public List<Long> updateRequestIdList = new ArrayList<>();
-
-    /** The key that was last passed to remove. */
-    public K lastRemoveKey;
-
-    /** The cache name that was last passed to removeAll. */
-    public String lastRemoveAllCacheName;
-
-    /**
-     * @param cacheName
-     * @param key
-     * @param requesterId
-     * @return null
-     */
-    @Override
-    public ICacheElement<K, V> get( String cacheName, K key, long requesterId )
-    {
-        lastGetKey = key;
-        return null;
-    }
-
-    /**
-     * @param cacheName
-     * @return empty set
-     */
-    @Override
-    public Set<K> getKeySet( String cacheName )
-    {
-        return new HashSet<>();
-    }
-
-    /**
-     * Set the last remove key.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @param requesterId
-     */
-    @Override
-    public void remove( String cacheName, K key, long requesterId )
-    {
-        lastRemoveKey = key;
-    }
-
-    /**
-     * Set the lastRemoveAllCacheName to the cacheName.
-     */
-    @Override
-    public void removeAll( String cacheName, long requesterId )
-        throws IOException
-    {
-        lastRemoveAllCacheName = cacheName;
-    }
-
-    /**
-     * Set the last update item.
-     * <p>
-     * @param item
-     * @param requesterId
-     */
-    @Override
-    public void update( ICacheElement<K, V> item, long requesterId )
-    {
-        lastUpdate = item;
-        updateRequestList.add( item );
-        updateRequestIdList.add( Long.valueOf( requesterId ) );
-    }
-
-    /**
-     * Do nothing.
-     * <p>
-     * @param cacheName
-     */
-    @Override
-    public void dispose( String cacheName )
-    {
-        return;
-    }
-
-    /**
-     * @param cacheName
-     * @param key
-     * @return null
-     */
-    @Override
-    public ICacheElement<K, V> get( String cacheName, K key )
-    {
-        return get( cacheName, key, 0 );
-    }
-
-    /**
-     * Do nothing.
-     */
-    @Override
-    public void release()
-    {
-        return;
-    }
-
-    /**
-     * Set the last remove key.
-     * <p>
-     * @param cacheName
-     * @param key
-     */
-    @Override
-    public void remove( String cacheName, K key )
-    {
-        lastRemoveKey = key;
-    }
-
-    /**
-     * Set the last remove all cache name.
-     * <p>
-     * @param cacheName
-     */
-    @Override
-    public void removeAll( String cacheName )
-    {
-        lastRemoveAllCacheName = cacheName;
-    }
-
-    /**
-     * Set the last update item.
-     * <p>
-     * @param item
-     */
-    @Override
-    public void update( ICacheElement<K, V> item )
-    {
-        lastUpdate = item;
-    }
-
-    /**
-     * @param cacheName
-     * @param keys
-     * @param requesterId
-     * @return empty map
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys, long requesterId )
-    {
-        lastGetMultipleKeys = keys;
-        return new HashMap<>();
-    }
-
-    /**
-     * @param cacheName
-     * @param keys
-     * @return empty map
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys )
-    {
-        return getMultiple( cacheName, keys, 0 );
-    }
-
-    /**
-     * Returns an empty map. Zombies have no internal data.
-     * <p>
-     * @param cacheName
-     * @param pattern
-     * @return an empty map
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern )
-        throws IOException
-    {
-        return getMatching( cacheName, pattern, 0 );
-    }
-
-    /**
-     * @param cacheName
-     * @param pattern
-     * @param requesterId
-     * @return Map
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern, long requesterId )
-        throws IOException
-    {
-        lastGetMatchingPattern = pattern;
-        return new HashMap<>();
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheClientTester.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheClientTester.java
deleted file mode 100644
index cc62371..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheClientTester.java
+++ /dev/null
@@ -1,342 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.access.exception.CacheException;
-import org.apache.commons.jcs.access.exception.ObjectExistsException;
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheConstants;
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheListener;
-import org.apache.commons.jcs.auxiliary.remote.server.behavior.RemoteType;
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheObserver;
-import org.apache.commons.jcs.engine.behavior.ICacheService;
-
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.rmi.Naming;
-import java.rmi.NotBoundException;
-import java.rmi.Remote;
-import java.rmi.registry.Registry;
-import java.rmi.server.ExportException;
-import java.rmi.server.UnicastRemoteObject;
-
-/**
- * Manual tester.
- */
-public class RemoteCacheClientTester
-    implements IRemoteCacheListener<String, String>, IRemoteCacheConstants, Remote
-{
-    /** the observer */
-    protected ICacheObserver watch;
-
-    /** the service */
-    protected ICacheService<String, String> cache;
-
-    /** The registry host name. */
-    final String host;
-
-    /** The registry port number. */
-    final int port;
-
-    /** call count */
-    final int count;
-
-    /** Description of the Field */
-    protected static long listenerId = 0;
-
-    /**
-     * Gets the remoteType attribute of the RemoteCacheClientTest object
-     * @return The remoteType value
-     * @throws IOException
-     */
-    @Override
-    public RemoteType getRemoteType()
-        throws IOException
-    {
-        return RemoteType.LOCAL;
-    }
-
-    /**
-     * Constructor for the RemoteCacheClientTest object
-     * @param count
-     * @throws MalformedURLException
-     * @throws NotBoundException
-     * @throws IOException
-     */
-    public RemoteCacheClientTester( int count )
-        throws MalformedURLException, NotBoundException, IOException
-    {
-        this( count, true, true, false );
-    }
-
-    /**
-     * Constructor for the RemoteCacheClientTest object
-     * @param count
-     * @param write
-     * @param read
-     * @param delete
-     * @throws MalformedURLException
-     * @throws NotBoundException
-     * @throws IOException
-     */
-    public RemoteCacheClientTester( int count, boolean write, boolean read, boolean delete )
-        throws MalformedURLException, NotBoundException, IOException
-    {
-        this( "", Registry.REGISTRY_PORT, count, write, read, delete );
-    }
-
-    /**
-     * Constructor for the RemoteCacheClientTest object
-     * @param host
-     * @param port
-     * @param count
-     * @param write
-     * @param read
-     * @param delete
-     * @throws MalformedURLException
-     * @throws NotBoundException
-     * @throws IOException
-     */
-    @SuppressWarnings("unchecked")
-    public RemoteCacheClientTester( String host, int port, int count, boolean write, boolean read, boolean delete )
-        throws MalformedURLException, NotBoundException, IOException
-    {
-        this.count = count;
-        this.host = host;
-        this.port = port;
-        // record export exception
-        Exception ee = null;
-
-        try
-        {
-            // Export this remote object to make it available to receive
-            // incoming calls,
-            // using an anonymous port.
-            UnicastRemoteObject.exportObject( this );
-        }
-        catch ( ExportException e )
-        {
-            // use already exported object; remember exception
-            ee = e;
-            ee.printStackTrace();
-        }
-        String service = System.getProperty( REMOTE_CACHE_SERVICE_NAME );
-
-        if ( service == null )
-        {
-            service = REMOTE_CACHE_SERVICE_VAL;
-        }
-        String registry = RemoteUtils.getNamingURL(host, port, service);
-
-        p( "looking up server " + registry );
-
-        Object obj = Naming.lookup( registry );
-
-        p( "server found" );
-
-        cache = (ICacheService<String, String>) obj;
-        watch = (ICacheObserver) obj;
-
-        p( "subscribing to the server" );
-
-        watch.addCacheListener( "testCache", this );
-        ICacheElement<String, String> cb = new CacheElement<>( "testCache", "testKey", "testVal" );
-
-        for ( int i = 0; i < count; i++ )
-        {
-            cb = new CacheElement<>( "testCache", "" + i, "" + i );
-
-            if ( delete )
-            {
-                p( "deleting a cache item from the server " + i );
-
-                cache.remove( cb.getCacheName(), cb.getKey() );
-            }
-            if ( write )
-            {
-                p( "putting a cache bean to the server " + i );
-
-                try
-                {
-                    cache.update( cb );
-                }
-                catch ( ObjectExistsException oee )
-                {
-                    p( oee.toString() );
-                }
-            }
-            if ( read )
-            {
-                try
-                {
-                    Object val = cache.get( cb.getCacheName(), cb.getKey() );
-                    p( "get " + cb.getKey() + " returns " + val );
-                }
-                catch ( CacheException onfe )
-                {
-                    // nothing
-                }
-            }
-        }
-    }
-
-    /**
-     * @param cb
-     * @throws IOException
-     */
-    @Override
-    public void handlePut( ICacheElement<String, String> cb )
-        throws IOException
-    {
-        p( "handlePut> cb=" + cb );
-    }
-
-    /**
-     * @param cacheName
-     * @param key
-     * @throws IOException
-     */
-    @Override
-    public void handleRemove( String cacheName, String key )
-        throws IOException
-    {
-        p( "handleRemove> cacheName=" + cacheName + ", key=" + key );
-    }
-
-    /**
-     * @param cacheName
-     * @throws IOException
-     */
-    @Override
-    public void handleRemoveAll( String cacheName )
-        throws IOException
-    {
-        p( "handleRemove> cacheName=" + cacheName );
-    }
-
-    /**
-     * @param cacheName
-     * @throws IOException
-     */
-    @Override
-    public void handleDispose( String cacheName )
-        throws IOException
-    {
-        p( "handleDispose> cacheName=" + cacheName );
-    }
-
-    /*
-     * public void handleRelease() throws IOException { p("handleRelease>"); }
-     */
-    /**
-     * The main program for the RemoteCacheClientTest class
-     * @param args The command line arguments
-     * @throws Exception
-     */
-    public static void main( String[] args )
-        throws Exception
-    {
-        int count = 0;
-        boolean read = false;
-        boolean write = false;
-        boolean delete = false;
-
-        for ( int i = 0; i < args.length; i++ )
-        {
-            if ( args[i].startsWith( "-" ) )
-            {
-                if ( !read )
-                {
-                    read = args[i].indexOf( "r" ) != -1;
-                }
-                if ( !write )
-                {
-                    write = args[i].indexOf( "w" ) != -1;
-                }
-                if ( !delete )
-                {
-                    delete = args[i].indexOf( "d" ) != -1;
-                }
-            }
-            else
-            {
-                count = Integer.parseInt( args[i] );
-            }
-        }
-        new RemoteCacheClientTester( count, write, read, delete );
-    }
-
-    /**
-     * Sets the listenerId attribute of the RemoteCacheClientTest object
-     * @param id The new listenerId value
-     * @throws IOException
-     */
-    @Override
-    public void setListenerId( long id )
-        throws IOException
-    {
-        listenerId = id;
-        p( "listenerId = " + id );
-    }
-
-    /**
-     * Gets the listenerId attribute of the RemoteCacheClientTest object
-     * @return The listenerId value
-     * @throws IOException
-     */
-    @Override
-    public long getListenerId()
-        throws IOException
-    {
-        return listenerId;
-    }
-
-    /**
-     * Helper for output, this is an user run test class
-     * @param s
-     */
-    private static void p( String s )
-    {
-        System.out.println( s );
-    }
-
-    /**
-     * @return null
-     * @throws IOException
-     */
-    @Override
-    public String getLocalHostAddress()
-        throws IOException
-    {
-        // TODO Auto-generated method stub
-        return null;
-    }
-
-    /**
-     * @throws IOException
-     */
-    @Override
-    public void dispose()
-        throws IOException
-    {
-        // TODO Auto-generated method stub
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheListenerUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheListenerUnitTest.java
deleted file mode 100644
index 7cc7e7c..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheListenerUnitTest.java
+++ /dev/null
@@ -1,124 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheAttributes;
-import org.apache.commons.jcs.engine.CacheElementSerialized;
-import org.apache.commons.jcs.engine.ElementAttributes;
-import org.apache.commons.jcs.engine.behavior.ICache;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheElementSerialized;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.engine.control.MockCompositeCacheManager;
-import org.apache.commons.jcs.utils.serialization.StandardSerializer;
-
-/**
- * Tests for the remote cache listener.
- * <p>
- * @author Aaron Smuts
- */
-public class RemoteCacheListenerUnitTest
-    extends TestCase
-{
-    /**
-     * Create a RemoteCacheListener with a mock cache manager.  Set remove on put to false.
-     * Create a serialized element.  Call put on the listener.
-     * Verify that the deserialized element is in the cache.
-     * <p>
-     * @throws Exception
-     */
-    public void testUpdate_PutOnPut()
-        throws Exception
-    {
-        // SETUP
-        IRemoteCacheAttributes irca = new RemoteCacheAttributes();
-        irca.setRemoveUponRemotePut( false );
-        ICompositeCacheManager cacheMgr = new MockCompositeCacheManager();
-        RemoteCacheListener<String, String> listener = new RemoteCacheListener<>( irca, cacheMgr, new StandardSerializer() );
-
-        String cacheName = "testName";
-        String key = "key";
-        String value = "value fdsadf dsafdsa fdsaf dsafdsaf dsafdsaf dsaf dsaf dsaf dsafa dsaf dsaf dsafdsaf";
-        IElementAttributes attr = new ElementAttributes();
-        attr.setMaxLife(34);
-
-        IElementSerializer elementSerializer = new StandardSerializer();
-
-        ICacheElementSerialized<String, String> element =
-            new CacheElementSerialized<>( cacheName, key, elementSerializer
-            .serialize( value ), attr );
-
-        // DO WORK
-        listener.handlePut( element );
-
-        // VERIFY
-        ICache<String, String> cache = cacheMgr.getCache( cacheName );
-        ICacheElement<String, String> after = cache.get( key );
-
-        assertNotNull( "Should have a deserialized object.", after );
-        assertEquals( "Values should be the same.", value, after.getVal() );
-        assertEquals( "Attributes should be the same.", attr.getMaxLife(), after
-            .getElementAttributes().getMaxLife() );
-        assertEquals( "Keys should be the same.", key, after.getKey() );
-        assertEquals( "Cache name should be the same.", cacheName, after.getCacheName() );
-    }
-
-    /**
-     * Create a RemoteCacheListener with a mock cache manager.  Set remove on put to true.
-     * Create a serialized element.  Call put on the listener.
-     * Verify that the deserialized element is not in the cache.
-     * <p>
-     * @throws Exception
-     */
-    public void testUpdate_RemoveOnPut()
-        throws Exception
-    {
-        // SETUP
-        IRemoteCacheAttributes irca = new RemoteCacheAttributes();
-        irca.setRemoveUponRemotePut( true );
-        ICompositeCacheManager cacheMgr = new MockCompositeCacheManager();
-        RemoteCacheListener<String, String> listener = new RemoteCacheListener<>( irca, cacheMgr, new StandardSerializer() );
-
-        String cacheName = "testName";
-        String key = "key";
-        String value = "value fdsadf dsafdsa fdsaf dsafdsaf dsafdsaf dsaf dsaf dsaf dsafa dsaf dsaf dsafdsaf";
-        IElementAttributes attr = new ElementAttributes();
-        attr.setMaxLife(34);
-
-        IElementSerializer elementSerializer = new StandardSerializer();
-
-        ICacheElementSerialized<String, String> element =
-            new CacheElementSerialized<>( cacheName, key, elementSerializer
-            .serialize( value ), attr );
-
-        // DO WORK
-        listener.handlePut( element );
-
-        // VERIFY
-        ICache<String, String> cache = cacheMgr.getCache( cacheName );
-        ICacheElement<String, String> after = cache.get( key );
-
-        assertNull( "Should not have a deserialized object since remove on put is true.", after );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheNoWaitFacadeUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheNoWaitFacadeUnitTest.java
deleted file mode 100644
index 8af3017..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheNoWaitFacadeUnitTest.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheAttributes;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-
-/**
- * Tests for RemoteCacheNoWaitFacade.
- */
-public class RemoteCacheNoWaitFacadeUnitTest
-    extends TestCase
-{
-    /**
-     * Verify that we can add an item.
-     */
-    public void testAddNoWait_InList()
-    {
-        // SETUP
-        List<RemoteCacheNoWait<String, String>> noWaits = new ArrayList<>();
-        IRemoteCacheAttributes cattr = new RemoteCacheAttributes();
-        cattr.setCacheName( "testCache1" );
-
-        RemoteCache<String, String> client = new RemoteCache<>(cattr, null, null, null);
-        RemoteCacheNoWait<String, String> noWait = new RemoteCacheNoWait<>( client );
-        noWaits.add( noWait );
-
-        RemoteCacheNoWaitFacade<String, String> facade = new RemoteCacheNoWaitFacade<>(noWaits, cattr, null, null, null );
-        
-        // VERIFY
-        assertEquals( "Should have one entry.", 1, facade.noWaits.size() );
-        assertTrue( "Should be in the list.", facade.noWaits.contains( noWait ) );
-        assertSame( "Should have same facade.", facade, ((RemoteCache<String, String>)facade.noWaits.get(0).getRemoteCache()).getFacade() );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheNoWaitUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheNoWaitUnitTest.java
deleted file mode 100644
index 93a7236..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheNoWaitUnitTest.java
+++ /dev/null
@@ -1,211 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.CacheStatus;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.utils.timing.SleepUtil;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-
-/**
- * Unit tests for the remote cache no wait. The no wait manages a queue on top of the client.
- * <p>
- * @author Aaron Smuts
- */
-public class RemoteCacheNoWaitUnitTest
-    extends TestCase
-{
-    /**
-     * Simply verify that the client gets updated via the no wait.
-     * <p>
-     * @throws Exception
-     */
-    public void testUpdate()
-        throws Exception
-    {
-        // SETUP
-        MockRemoteCacheClient<String, String> client = new MockRemoteCacheClient<>();
-        RemoteCacheNoWait<String, String> noWait = new RemoteCacheNoWait<>( client );
-
-        ICacheElement<String, String> element = new CacheElement<>( "testUpdate", "key", "value" );
-
-        // DO WORK
-        noWait.update( element );
-
-        // VERIFY
-        SleepUtil.sleepAtLeast( 10 );
-
-        assertEquals( "Wrong number updated.", 1, client.updateList.size() );
-        assertEquals( "Wrong element", element, client.updateList.get( 0 ) );
-    }
-
-    /**
-     * Simply verify that the client get is called from the no wait.
-     * <p>
-     * @throws Exception
-     */
-    public void testGet()
-        throws Exception
-    {
-        // SETUP
-        MockRemoteCacheClient<String, String> client = new MockRemoteCacheClient<>();
-        RemoteCacheNoWait<String, String> noWait = new RemoteCacheNoWait<>( client );
-
-        ICacheElement<String, String> input = new CacheElement<>( "testUpdate", "key", "value" );
-        client.getSetupMap.put( "key", input );
-
-        // DO WORK
-        ICacheElement<String, String> result = noWait.get( "key" );
-
-        // VERIFY
-        assertEquals( "Wrong element", input, result );
-    }
-
-    /**
-     * Simply verify that the client getMultiple is called from the no wait.
-     * <p>
-     * @throws Exception
-     */
-    public void testGetMultiple()
-        throws Exception
-    {
-        // SETUP
-        MockRemoteCacheClient<String, String> client = new MockRemoteCacheClient<>();
-        RemoteCacheNoWait<String, String> noWait = new RemoteCacheNoWait<>( client );
-
-        ICacheElement<String, String> inputElement = new CacheElement<>( "testUpdate", "key", "value" );
-        Map<String, ICacheElement<String, String>> inputMap = new HashMap<>();
-        inputMap.put( "key", inputElement );
-
-        Set<String> keys = new HashSet<>();
-        keys.add( "key" );
-
-        client.getMultipleSetupMap.put( keys, inputMap );
-
-        // DO WORK
-        Map<String, ICacheElement<String, String>> result = noWait.getMultiple( keys );
-
-        // VERIFY
-        assertEquals( "elements map", inputMap, result );
-    }
-
-    /**
-     * Simply verify that the client gets updated via the no wait.
-     * <p>
-     * @throws Exception
-     */
-    public void testRemove()
-        throws Exception
-    {
-        // SETUP
-        MockRemoteCacheClient<String, String> client = new MockRemoteCacheClient<>();
-        RemoteCacheNoWait<String, String> noWait = new RemoteCacheNoWait<>( client );
-
-        String input = "MyKey";
-
-        // DO WORK
-        noWait.remove( input );
-
-        SleepUtil.sleepAtLeast( 10 );
-
-        // VERIFY
-        assertEquals( "Wrong number updated.", 1, client.removeList.size() );
-        assertEquals( "Wrong key", input, client.removeList.get( 0 ) );
-    }
-
-    /**
-     * Simply verify that the client status is returned in the stats.
-     * <p>
-     * @throws Exception
-     */
-    public void testGetStats()
-        throws Exception
-    {
-        // SETUP
-        MockRemoteCacheClient<String, String> client = new MockRemoteCacheClient<>();
-        client.status = CacheStatus.ALIVE;
-        RemoteCacheNoWait<String, String> noWait = new RemoteCacheNoWait<>( client );
-
-        // DO WORK
-        String result = noWait.getStats();
-
-        // VERIFY
-        assertTrue( "Status should contain 'ALIVE'", result.indexOf( "ALIVE" ) != -1 );
-    }
-
-    /**
-     * Simply verify that we get a status of error if the cache is in error..
-     * <p>
-     * @throws Exception
-     */
-    public void testGetStatus_error()
-        throws Exception
-    {
-        // SETUP
-        MockRemoteCacheClient<String, String> client = new MockRemoteCacheClient<>();
-        client.status = CacheStatus.ERROR;
-        RemoteCacheNoWait<String, String> noWait = new RemoteCacheNoWait<>( client );
-
-        // DO WORK
-        CacheStatus result = noWait.getStatus();
-
-        // VERIFY
-        assertEquals( "Wrong status", CacheStatus.ERROR, result );
-    }
-
-    /**
-     * Simply verify that the serviced supplied to fix is passed onto the client. Verify that the
-     * original event queue is destroyed. A new event queue willbe plugged in on fix.
-     * <p>
-     * @throws Exception
-     */
-    public void testFixCache()
-        throws Exception
-    {
-        // SETUP
-        MockRemoteCacheClient<String, String> client = new MockRemoteCacheClient<>();
-        client.status = CacheStatus.ALIVE;
-        RemoteCacheNoWait<String, String> noWait = new RemoteCacheNoWait<>( client );
-
-        MockRemoteCacheService<String, String> service = new MockRemoteCacheService<>();
-
-        ICacheElement<String, String> element = new CacheElement<>( "testUpdate", "key", "value" );
-
-        // DO WORK
-        noWait.update( element );
-        SleepUtil.sleepAtLeast( 10 );
-        // ICacheEventQueue<String, String> originalQueue = noWait.getCacheEventQueue();
-
-        noWait.fixCache( service );
-
-        noWait.update( element );
-        SleepUtil.sleepAtLeast( 10 );
-
-        // VERIFY
-        assertEquals( "Wrong status", service, client.fixed );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheUnitTest.java
deleted file mode 100644
index 55090d4..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/RemoteCacheUnitTest.java
+++ /dev/null
@@ -1,295 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.HashSet;
-import java.util.Map;
-
-import junit.framework.TestCase;
-
-import org.apache.commons.jcs.auxiliary.MockCacheEventLogger;
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheAttributes;
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.ZombieCacheServiceNonLocal;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheElementSerialized;
-import org.apache.commons.jcs.utils.serialization.SerializationConversionUtil;
-
-/**
- * Unit Tests for the Remote Cache.
- */
-public class RemoteCacheUnitTest
-    extends TestCase
-{
-    private IRemoteCacheAttributes cattr;
-    private MockRemoteCacheService<String, String> service;
-    private MockRemoteCacheListener<String, String> listener;
-    private RemoteCacheMonitor monitor;
-
-    /**
-     * @see junit.framework.TestCase#setUp()
-     */
-    @Override
-    protected void setUp() throws Exception
-    {
-        super.setUp();
-        cattr = new RemoteCacheAttributes();
-        service = new MockRemoteCacheService<>();
-        listener = new MockRemoteCacheListener<>();
-        monitor = new RemoteCacheMonitor();
-    }
-
-    /**
-     * Verify that the remote service update method is called. The remote cache serializes the object
-     * first.
-     * <p>
-     * @throws Exception
-     */
-    public void testUpdate()
-        throws Exception
-    {
-        // SETUP
-        long listenerId = 123;
-        listener.setListenerId( listenerId );
-
-        RemoteCache<String, String> remoteCache = new RemoteCache<>( cattr, service, listener, monitor );
-
-        String cacheName = "testUpdate";
-
-        // DO WORK
-        ICacheElement<String, String> element = new CacheElement<>( cacheName, "key", "value" );
-        remoteCache.update( element );
-
-        // VERIFY
-        assertTrue( "The element should be in the serialized wrapper.",
-                    service.lastUpdate instanceof ICacheElementSerialized );
-        ICacheElement<String, String> result = SerializationConversionUtil
-            .getDeSerializedCacheElement( (ICacheElementSerialized<String, String>) service.lastUpdate, remoteCache
-                .getElementSerializer() );
-        assertEquals( "Wrong element updated.", element.getVal(), result.getVal() );
-        assertEquals( "Wrong listener id.", Long.valueOf( listenerId ), service.updateRequestIdList.get( 0 ) );
-    }
-
-    /**
-     * Verify that when we call fix events queued in the zombie are propagated to the new service.
-     * <p>
-     * @throws Exception
-     */
-    public void testUpdateZombieThenFix()
-        throws Exception
-    {
-        // SETUP
-        ZombieCacheServiceNonLocal<String, String> zombie = new ZombieCacheServiceNonLocal<>( 10 );
-
-        // set the zombie
-        RemoteCache<String, String> remoteCache = new RemoteCache<>( cattr, zombie, listener, monitor );
-
-        String cacheName = "testUpdate";
-
-        // DO WORK
-        ICacheElement<String, String> element = new CacheElement<>( cacheName, "key", "value" );
-        remoteCache.update( element );
-        // set the new service, this should call propagate
-        remoteCache.fixCache( service );
-
-        // VERIFY
-        assertTrue( "The element should be in the serialized warapper.",
-                    service.lastUpdate instanceof ICacheElementSerialized );
-        ICacheElement<String, String> result = SerializationConversionUtil
-            .getDeSerializedCacheElement( (ICacheElementSerialized<String, String>) service.lastUpdate, remoteCache
-                .getElementSerializer() );
-        assertEquals( "Wrong element updated.", element.getVal(), result.getVal() );
-    }
-
-    /**
-     * Verify event log calls.
-     * <p>
-     * @throws Exception
-     */
-    public void testUpdate_simple()
-        throws Exception
-    {
-        RemoteCache<String, String> remoteCache = new RemoteCache<>( cattr, service, listener, monitor );
-
-        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
-        remoteCache.setCacheEventLogger( cacheEventLogger );
-
-        ICacheElement<String, String> item = new CacheElement<>( "region", "key", "value" );
-
-        // DO WORK
-        remoteCache.update( item );
-
-        // VERIFY
-        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
-        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
-    }
-
-    /**
-     * Verify event log calls.
-     * <p>
-     * @throws Exception
-     */
-    public void testGet_simple()
-        throws Exception
-    {
-        RemoteCache<String, String> remoteCache = new RemoteCache<>( cattr, service, listener, monitor );
-
-        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
-        remoteCache.setCacheEventLogger( cacheEventLogger );
-
-        // DO WORK
-        remoteCache.get( "key" );
-
-        // VERIFY
-        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
-        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
-    }
-
-    /**
-     * Verify event log calls.
-     * <p>
-     * @throws Exception
-     */
-    public void testGetMultiple_simple()
-        throws Exception
-    {
-        RemoteCache<String, String> remoteCache = new RemoteCache<>( cattr, service, listener, monitor );
-
-        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
-        remoteCache.setCacheEventLogger( cacheEventLogger );
-
-        // DO WORK
-        remoteCache.getMultiple( new HashSet<>() );
-
-        // VERIFY
-        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
-        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
-    }
-
-    /**
-     * Verify event log calls.
-     * <p>
-     * @throws Exception
-     */
-    public void testRemove_simple()
-        throws Exception
-    {
-        RemoteCache<String, String> remoteCache = new RemoteCache<>( cattr, service, listener, monitor );
-
-        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
-        remoteCache.setCacheEventLogger( cacheEventLogger );
-
-        // DO WORK
-        remoteCache.remove( "key" );
-
-        // VERIFY
-        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
-        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
-    }
-
-    /**
-     * Verify event log calls.
-     * <p>
-     * @throws Exception
-     */
-    public void testRemoveAll_simple()
-        throws Exception
-    {
-        RemoteCache<String, String> remoteCache = new RemoteCache<>( cattr, service, listener, monitor );
-
-        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
-        remoteCache.setCacheEventLogger( cacheEventLogger );
-
-        // DO WORK
-        remoteCache.remove( "key" );
-
-        // VERIFY
-        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
-        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
-    }
-
-    /**
-     * Verify event log calls.
-     * <p>
-     * @throws Exception
-     */
-    public void testGetMatching_simple()
-        throws Exception
-    {
-        // SETUP
-        String pattern = "adsfasdfasd.?";
-
-        RemoteCache<String, String> remoteCache = new RemoteCache<>( cattr, service, listener, monitor );
-
-        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
-        remoteCache.setCacheEventLogger( cacheEventLogger );
-
-        // DO WORK
-        Map<String, ICacheElement<String, String>> result = remoteCache.getMatching( pattern );
-
-        // VERIFY
-        assertNotNull( "Should have a map", result );
-        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
-        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
-    }
-
-    /**
-     * Verify event log calls.
-     * <p>
-     * @throws Exception
-     */
-    public void testDispose_simple()
-        throws Exception
-    {
-        RemoteCache<String, String> remoteCache = new RemoteCache<>( cattr, service, listener, monitor );
-
-        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
-        remoteCache.setCacheEventLogger( cacheEventLogger );
-
-        // DO WORK
-        remoteCache.dispose( );
-
-        // VERIFY
-        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
-        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
-    }
-
-    /**
-     * Verify that there is no problem if there is no listener.
-     * <p>
-     * @throws Exception
-     */
-    public void testDispose_nullListener()
-        throws Exception
-    {
-        // SETUP
-        RemoteCache<String, String> remoteCache = new RemoteCache<>( cattr, service, null, monitor );
-
-        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
-        remoteCache.setCacheEventLogger( cacheEventLogger );
-
-        // DO WORK
-        remoteCache.dispose( );
-
-        // VERIFY
-        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
-        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/RemoteUtilsUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/RemoteUtilsUnitTest.java
deleted file mode 100644
index 344b521..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/RemoteUtilsUnitTest.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.rmi.registry.Registry;
-
-import junit.framework.TestCase;
-
-/**
- * Simple tests for remote utils. It is difficult to verify most of the things is does.
- *<p>
- * @author Aaron Smuts
- */
-public class RemoteUtilsUnitTest
-    extends TestCase
-{
-    /**
-     * Call create registry.
-     * <p>
-     * The exception is in the security manager setting.
-     */
-    public void testCreateRegistry()
-    {
-        Registry registry = RemoteUtils.createRegistry( 1102 );
-        assertNotNull("Registry should not be null", registry);
-    }
-
-    public void testGetNamingURL()
-    {
-        assertEquals("//host:1/servicename", RemoteUtils.getNamingURL("host",1,"servicename"));
-        assertEquals("//127.0.0.1:2/servicename", RemoteUtils.getNamingURL("127.0.0.1",2,"servicename"));
-        assertEquals("//[0:0:0:0:0:0:0:1%251]:3/servicename", RemoteUtils.getNamingURL("0:0:0:0:0:0:0:1%1",3,"servicename"));
-    }
-
-    public void testParseServerAndPort()
-    {
-        RemoteLocation loc = RemoteLocation.parseServerAndPort("server1:1234");
-        assertEquals("server1", loc.getHost());
-        assertEquals(1234, loc.getPort());
-
-        loc = RemoteLocation.parseServerAndPort("  server2  :  4567  ");
-        assertEquals("server2", loc.getHost());
-        assertEquals(4567, loc.getPort());
-
-        loc = RemoteLocation.parseServerAndPort("server2  :  port");
-        assertNull(loc);
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/TestRemoteCache.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/TestRemoteCache.java
deleted file mode 100644
index f7c9666..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/TestRemoteCache.java
+++ /dev/null
@@ -1,141 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote;
-
-import java.util.Properties;
-
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.auxiliary.AuxiliaryCache;
-import org.apache.commons.jcs.auxiliary.MockCacheEventLogger;
-import org.apache.commons.jcs.auxiliary.remote.server.RemoteCacheServerFactory;
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
-import org.apache.commons.jcs.engine.control.MockCompositeCacheManager;
-import org.apache.commons.jcs.engine.control.MockElementSerializer;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-
-/**
- * @author Aaron SMuts
- */
-public class TestRemoteCache
-    extends TestCase
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog( TestRemoteCache.class );
-
-    /**
-     * Start the cache.
-     */
-    public TestRemoteCache()
-    {
-        super();
-        try
-        {
-            System.out.println( "main> creating registry on the localhost" );
-            RemoteUtils.createRegistry( 1101 );
-            Properties config = RemoteUtils.loadProps("/TestRemoteServer.ccf");
-
-            RemoteCacheServerFactory.startup( "localhost", 1101, config);
-        }
-        catch ( Exception e )
-        {
-            e.printStackTrace();
-        }
-    }
-
-    /**
-     * Test setup
-     */
-    @Override
-    public void setUp()
-    {
-        JCS.setConfigFilename( "/TestRemoteClient.ccf" );
-    }
-
-    /**
-     * @throws Exception
-     *
-     *
-     */
-    public void skiptestSimpleSend()
-        throws Exception
-    {
-        log.info( "testSimpleSend" );
-
-        CacheAccess<String, String> cache = JCS.getInstance( "testCache" );
-
-        log.info( "cache = " + cache );
-
-        for ( int i = 0; i < 1000; i++ )
-        {
-//            System.out.println( "puttting " + i );
-            cache.put( "key" + i, "data" + i );
-//            System.out.println( "put " + i );
-            log.info( "put " + i );
-        }
-    }
-
-    /**
-     * @throws Exception
-     */
-    public void testService()
-        throws Exception
-    {
-
-        Thread.sleep( 100 );
-
-        ICompositeCacheManager cacheMgr = new MockCompositeCacheManager();
-
-        RemoteCacheAttributes rca = new RemoteCacheAttributes();
-        rca.setRemoteLocation( "localhost", 1101 );
-        rca.setCacheName( "testCache" );
-
-        RemoteCacheFactory factory = new RemoteCacheFactory();
-        factory.initialize();
-        RemoteCacheManager mgr = factory.getManager( rca, cacheMgr, new MockCacheEventLogger(), new MockElementSerializer() );
-        AuxiliaryCache<String, String> cache = mgr.getCache( rca );
-
-        int numMes = 100;
-        for ( int i = 0; i < numMes; i++ )
-        {
-            String message = "adsfasasfasfasdasf";
-            CacheElement<String, String> ce = new CacheElement<>( "key" + 1, "data" + i, message );
-            cache.update( ce );
-//            System.out.println( "put " + ce );
-        }
-
-        // Thread.sleep( 2000 );
-
-        /*
-         * // the receiver instance. JCS cacheReceiver = JCS.getInstance(
-         * "testCache" );
-         *
-         * log.info( "cache = " + cache );
-         *
-         * for ( int i = 0; i < numMes; i++ ) { System.out.println( "getting " +
-         * i ); Object data = cacheReceiver.get( "key" + i );
-         * System.out.println( i + " = " + data ); }
-         */
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/ZombieRemoteCacheServiceUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/ZombieRemoteCacheServiceUnitTest.java
deleted file mode 100644
index 84ecc3a..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/ZombieRemoteCacheServiceUnitTest.java
+++ /dev/null
@@ -1,127 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.ZombieCacheServiceNonLocal;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-
-/**
- * Tests for the zombie remote cache service.
- */
-public class ZombieRemoteCacheServiceUnitTest
-    extends TestCase
-{
-    /**
-     * Verify that an update event gets added and then is sent to the service passed to propagate.
-     * <p>
-     * @throws Exception
-     */
-    public void testUpdateThenWalk()
-        throws Exception
-    {
-        // SETUP
-        MockRemoteCacheService<String, String> service = new MockRemoteCacheService<>();
-
-        ZombieCacheServiceNonLocal<String, String> zombie = new ZombieCacheServiceNonLocal<>( 10 );
-
-        String cacheName = "testUpdate";
-
-        // DO WORK
-        ICacheElement<String, String> element = new CacheElement<>( cacheName, "key", "value" );
-        zombie.update( element, 123l );
-        zombie.propagateEvents( service );
-
-        // VERIFY
-        assertEquals( "Updated element is not as expected.", element, service.lastUpdate );
-    }
-
-    /**
-     * Verify that nothing is added if the max is set to 0.
-     * <p>
-     * @throws Exception
-     */
-    public void testUpdateThenWalk_zeroSize()
-        throws Exception
-    {
-        // SETUP
-        MockRemoteCacheService<String, String> service = new MockRemoteCacheService<>();
-
-        ZombieCacheServiceNonLocal<String, String> zombie = new ZombieCacheServiceNonLocal<>( 0 );
-
-        String cacheName = "testUpdate";
-
-        // DO WORK
-        ICacheElement<String, String> element = new CacheElement<>( cacheName, "key", "value" );
-        zombie.update( element, 123l );
-        zombie.propagateEvents( service );
-
-        // VERIFY
-        assertNull( "Nothing should have been put to the service.", service.lastUpdate );
-    }
-
-    /**
-     * Verify that a remove event gets added and then is sent to the service passed to propagate.
-     * <p>
-     * @throws Exception
-     */
-    public void testRemoveThenWalk()
-        throws Exception
-    {
-        // SETUP
-        MockRemoteCacheService<String, String> service = new MockRemoteCacheService<>();
-
-        ZombieCacheServiceNonLocal<String, String> zombie = new ZombieCacheServiceNonLocal<>( 10 );
-
-        String cacheName = "testRemoveThenWalk";
-        String key = "myKey";
-
-        // DO WORK
-        zombie.remove( cacheName, key, 123l );
-        zombie.propagateEvents( service );
-
-        // VERIFY
-        assertEquals( "Updated element is not as expected.", key, service.lastRemoveKey );
-    }
-
-    /**
-     * Verify that a removeAll event gets added and then is sent to the service passed to propagate.
-     * <p>
-     * @throws Exception
-     */
-    public void testRemoveAllThenWalk()
-        throws Exception
-    {
-        // SETUP
-        MockRemoteCacheService<String, String> service = new MockRemoteCacheService<>();
-
-        ZombieCacheServiceNonLocal<String, String> zombie = new ZombieCacheServiceNonLocal<>( 10 );
-
-        String cacheName = "testRemoveThenWalk";
-
-        // DO WORK
-        zombie.removeAll( cacheName, 123l );
-        zombie.propagateEvents( service );
-
-        // VERIFY
-        assertEquals( "Updated element is not as expected.", cacheName, service.lastRemoveAllCacheName);
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/http/client/MockRemoteCacheDispatcher.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/http/client/MockRemoteCacheDispatcher.java
deleted file mode 100644
index e3986db..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/http/client/MockRemoteCacheDispatcher.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.http.client;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheDispatcher;
-import org.apache.commons.jcs.auxiliary.remote.value.RemoteCacheRequest;
-import org.apache.commons.jcs.auxiliary.remote.value.RemoteCacheResponse;
-
-import java.io.IOException;
-
-/** For testing the service. */
-public class MockRemoteCacheDispatcher
-    implements IRemoteCacheDispatcher
-{
-    /** The last request passes to dispatch */
-    public RemoteCacheRequest<?, ?> lastRemoteCacheRequest;
-
-    /** The response setup */
-    public RemoteCacheResponse<?> setupRemoteCacheResponse;
-
-    /** Records the last and returns setupRemoteCacheResponse.
-     * <p>
-     * @param remoteCacheRequest
-     * @return RemoteCacheResponse
-     * @throws IOException
-     */
-    @Override
-    @SuppressWarnings("unchecked")
-    public <K, V, T>
-        RemoteCacheResponse<T> dispatchRequest( RemoteCacheRequest<K, V> remoteCacheRequest )
-        throws IOException
-    {
-        this.lastRemoteCacheRequest = remoteCacheRequest;
-        return (RemoteCacheResponse<T>)setupRemoteCacheResponse;
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/http/client/RemoteHttpCacheClientUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/http/client/RemoteHttpCacheClientUnitTest.java
deleted file mode 100644
index 7d31c70..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/http/client/RemoteHttpCacheClientUnitTest.java
+++ /dev/null
@@ -1,275 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.http.client;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.auxiliary.remote.value.RemoteCacheResponse;
-import org.apache.commons.jcs.auxiliary.remote.value.RemoteRequestType;
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-/** Unit tests for the client. */
-public class RemoteHttpCacheClientUnitTest
-    extends TestCase
-{
-    /**
-     * Verify get functionality
-     * <p>
-     * @throws IOException
-     */
-    public void testGet_nullFromDispatcher()
-        throws IOException
-    {
-        // SETUP
-        RemoteHttpCacheAttributes attributes = new RemoteHttpCacheAttributes();
-        RemoteHttpCacheClient<String, String> client = new RemoteHttpCacheClient<>( attributes );
-
-        MockRemoteCacheDispatcher mockDispatcher = new MockRemoteCacheDispatcher();
-        client.setRemoteDispatcher( mockDispatcher );
-
-        String cacheName = "test";
-        String key = "key";
-
-        mockDispatcher.setupRemoteCacheResponse = null;
-
-        // DO WORK
-        ICacheElement<String, String> result = client.get( cacheName, key );
-
-        // VERIFY
-        assertNull( "Wrong result.", result );
-        assertEquals( "Wrong type.", RemoteRequestType.GET, mockDispatcher.lastRemoteCacheRequest
-            .getRequestType() );
-    }
-
-    /**
-     * Verify get functionality
-     * <p>
-     * @throws IOException
-     */
-    public void testGet_normal()
-        throws IOException
-    {
-        // SETUP
-        RemoteHttpCacheAttributes attributes = new RemoteHttpCacheAttributes();
-        RemoteHttpCacheClient<String, String> client = new RemoteHttpCacheClient<>( attributes );
-
-        MockRemoteCacheDispatcher mockDispatcher = new MockRemoteCacheDispatcher();
-        client.setRemoteDispatcher( mockDispatcher );
-
-        String cacheName = "test";
-        String key = "key";
-
-        ICacheElement<String, String> expected = new CacheElement<>( cacheName, key, "value" );
-        RemoteCacheResponse<ICacheElement<String, String>> remoteHttpCacheResponse =
-            new RemoteCacheResponse<>();
-        remoteHttpCacheResponse.setPayload( expected );
-
-        mockDispatcher.setupRemoteCacheResponse = remoteHttpCacheResponse;
-
-        // DO WORK
-        ICacheElement<String, String> result = client.get( cacheName, key );
-
-        // VERIFY
-        assertEquals( "Wrong result.", expected, result );
-        assertEquals( "Wrong type.", RemoteRequestType.GET, mockDispatcher.lastRemoteCacheRequest
-            .getRequestType() );
-    }
-
-    /**
-     * Verify get functionality
-     * <p>
-     * @throws IOException
-     */
-    public void testGetMatching_normal()
-        throws IOException
-    {
-        // SETUP
-        RemoteHttpCacheAttributes attributes = new RemoteHttpCacheAttributes();
-        RemoteHttpCacheClient<String, String> client = new RemoteHttpCacheClient<>( attributes );
-
-        MockRemoteCacheDispatcher mockDispatcher = new MockRemoteCacheDispatcher();
-        client.setRemoteDispatcher( mockDispatcher );
-
-        String cacheName = "test";
-        String pattern = "key";
-
-        ICacheElement<String, String> expected = new CacheElement<>( cacheName, "key", "value" );
-        Map<String, ICacheElement<String, String>> expectedMap = new HashMap<>();
-        expectedMap.put( "key", expected );
-        RemoteCacheResponse<Map<String, ICacheElement<String, String>>> remoteHttpCacheResponse =
-            new RemoteCacheResponse<>();
-        remoteHttpCacheResponse.setPayload( expectedMap );
-
-        mockDispatcher.setupRemoteCacheResponse = remoteHttpCacheResponse;
-
-        // DO WORK
-        Map<String, ICacheElement<String, String>> result = client.getMatching( cacheName, pattern );
-
-        // VERIFY
-        assertEquals( "Wrong result.", expected, result.get( "key" ) );
-        assertEquals( "Wrong type.", RemoteRequestType.GET_MATCHING,
-                      mockDispatcher.lastRemoteCacheRequest.getRequestType() );
-    }
-
-    /**
-     * Verify get functionality
-     * <p>
-     * @throws IOException
-     */
-    public void testGetMultiple_normal()
-        throws IOException
-    {
-        // SETUP
-        RemoteHttpCacheAttributes attributes = new RemoteHttpCacheAttributes();
-        RemoteHttpCacheClient<String, String> client = new RemoteHttpCacheClient<>( attributes );
-
-        MockRemoteCacheDispatcher mockDispatcher = new MockRemoteCacheDispatcher();
-        client.setRemoteDispatcher( mockDispatcher );
-
-        String cacheName = "test";
-        Set<String> keys = Collections.emptySet();
-
-        ICacheElement<String, String> expected = new CacheElement<>( cacheName, "key", "value" );
-        Map<String, ICacheElement<String, String>> expectedMap = new HashMap<>();
-        expectedMap.put( "key", expected );
-        RemoteCacheResponse<Map<String, ICacheElement<String, String>>> remoteHttpCacheResponse =
-            new RemoteCacheResponse<>();
-        remoteHttpCacheResponse.setPayload( expectedMap );
-
-        mockDispatcher.setupRemoteCacheResponse = remoteHttpCacheResponse;
-
-        // DO WORK
-        Map<String, ICacheElement<String, String>> result = client.getMultiple( cacheName, keys );
-
-        // VERIFY
-        assertEquals( "Wrong result.", expected, result.get( "key" ) );
-        assertEquals( "Wrong type.", RemoteRequestType.GET_MULTIPLE,
-                      mockDispatcher.lastRemoteCacheRequest.getRequestType() );
-    }
-
-    /**
-     * Verify remove functionality
-     * <p>
-     * @throws IOException
-     */
-    public void testRemove_normal()
-        throws IOException
-    {
-        // SETUP
-        RemoteHttpCacheAttributes attributes = new RemoteHttpCacheAttributes();
-        RemoteHttpCacheClient<String, String> client = new RemoteHttpCacheClient<>( attributes );
-
-        MockRemoteCacheDispatcher mockDispatcher = new MockRemoteCacheDispatcher();
-        client.setRemoteDispatcher( mockDispatcher );
-
-        String cacheName = "test";
-        String key = "key";
-
-        // DO WORK
-        client.remove( cacheName, key );
-
-        // VERIFY
-        assertEquals( "Wrong type.", RemoteRequestType.REMOVE, mockDispatcher.lastRemoteCacheRequest
-            .getRequestType() );
-    }
-
-    /**
-     * Verify removeall functionality
-     * <p>
-     * @throws IOException
-     */
-    public void testRemoveAll_normal()
-        throws IOException
-    {
-        // SETUP
-        RemoteHttpCacheAttributes attributes = new RemoteHttpCacheAttributes();
-        RemoteHttpCacheClient<String, String> client = new RemoteHttpCacheClient<>( attributes );
-
-        MockRemoteCacheDispatcher mockDispatcher = new MockRemoteCacheDispatcher();
-        client.setRemoteDispatcher( mockDispatcher );
-
-        String cacheName = "test";
-
-        // DO WORK
-        client.removeAll( cacheName );
-
-        // VERIFY
-        assertEquals( "Wrong type.", RemoteRequestType.REMOVE_ALL, mockDispatcher.lastRemoteCacheRequest
-            .getRequestType() );
-    }
-
-    /**
-     * Verify update functionality
-     * <p>
-     * @throws IOException
-     */
-    public void testUpdate_normal()
-        throws IOException
-    {
-        // SETUP
-        RemoteHttpCacheAttributes attributes = new RemoteHttpCacheAttributes();
-        RemoteHttpCacheClient<String, String> client = new RemoteHttpCacheClient<>( attributes );
-
-        MockRemoteCacheDispatcher mockDispatcher = new MockRemoteCacheDispatcher();
-        client.setRemoteDispatcher( mockDispatcher );
-
-        String cacheName = "test";
-
-        ICacheElement<String, String> element = new CacheElement<>( cacheName, "key", "value" );
-
-        // DO WORK
-        client.update( element );
-
-        // VERIFY
-        assertEquals( "Wrong type.", RemoteRequestType.UPDATE, mockDispatcher.lastRemoteCacheRequest
-            .getRequestType() );
-    }
-
-    /**
-     * Verify dispose functionality
-     * <p>
-     * @throws IOException
-     */
-    public void testDispose_normal()
-        throws IOException
-    {
-        // SETUP
-        RemoteHttpCacheAttributes attributes = new RemoteHttpCacheAttributes();
-        RemoteHttpCacheClient<String, String> client = new RemoteHttpCacheClient<>( attributes );
-
-        MockRemoteCacheDispatcher mockDispatcher = new MockRemoteCacheDispatcher();
-        client.setRemoteDispatcher( mockDispatcher );
-
-        String cacheName = "test";
-
-        // DO WORK
-        client.dispose( cacheName );
-
-        // VERIFY
-        assertEquals( "Wrong type.", RemoteRequestType.DISPOSE, mockDispatcher.lastRemoteCacheRequest
-            .getRequestType() );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/http/client/RemoteHttpCacheFactoryUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/http/client/RemoteHttpCacheFactoryUnitTest.java
deleted file mode 100644
index 0d5c20f..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/http/client/RemoteHttpCacheFactoryUnitTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.http.client;
-
-import org.apache.commons.jcs.auxiliary.AuxiliaryCache;
-import org.apache.commons.jcs.auxiliary.remote.http.client.behavior.IRemoteHttpCacheClient;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.engine.control.MockCompositeCacheManager;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-
-/** Unit tests for the manager. */
-public class RemoteHttpCacheFactoryUnitTest
-    extends TestCase
-{
-    /** Verify that we get the default. */
-    public void testCreateRemoteHttpCacheClient_Bad()
-    {
-        // SETUP
-        String remoteHttpClientClassName = "junk";
-        RemoteHttpCacheAttributes cattr = new RemoteHttpCacheAttributes();
-        cattr.setRemoteHttpClientClassName( remoteHttpClientClassName );
-
-        RemoteHttpCacheFactory factory = new RemoteHttpCacheFactory();
-
-        // DO WORK
-        IRemoteHttpCacheClient<String, String> result = factory.createRemoteHttpCacheClientForAttributes( cattr );
-
-        // VEIFY
-        assertNotNull( "Should have a cache.", result );
-        assertTrue( "Wrong default.", result instanceof RemoteHttpCacheClient );
-        assertTrue( "Should be initialized", ((RemoteHttpCacheClient<String, String>)result).isInitialized() );
-    }
-
-    /** Verify that we get the default. */
-    public void testCreateRemoteHttpCacheClient_default()
-    {
-        // SETUP
-        RemoteHttpCacheAttributes cattr = new RemoteHttpCacheAttributes();
-        RemoteHttpCacheFactory factory = new RemoteHttpCacheFactory();
-
-        // DO WORK
-        IRemoteHttpCacheClient<String, String> result = factory.createRemoteHttpCacheClientForAttributes( cattr );
-
-        // VEIFY
-        assertNotNull( "Should have a cache.", result );
-        assertTrue( "Wrong default.", result instanceof RemoteHttpCacheClient );
-    }
-
-    /** Verify that we get a cache no wait. */
-    public void testGetCache_normal()
-    {
-        // SETUP
-        ICompositeCacheManager cacheMgr = new MockCompositeCacheManager();
-        assertNotNull( "Should have a manager.", cacheMgr );
-        ICacheEventLogger cacheEventLogger = null;
-        IElementSerializer elementSerializer = null;
-
-        RemoteHttpCacheAttributes cattr = new RemoteHttpCacheAttributes();
-        assertNotNull( "Should have attributes.", cattr );
-        RemoteHttpCacheFactory factory = new RemoteHttpCacheFactory();
-        assertNotNull( "Should have a factory.", factory );
-
-
-        // DO WORK
-        AuxiliaryCache<String, String> result = factory.createCache(cattr, cacheMgr, cacheEventLogger, elementSerializer);
-
-        // VERIFY
-        assertNotNull( "Should have a cache.", result );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/http/client/RemoteHttpCacheManualTester.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/http/client/RemoteHttpCacheManualTester.java
deleted file mode 100644
index d65720c..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/http/client/RemoteHttpCacheManualTester.java
+++ /dev/null
@@ -1,75 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.http.client;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-
-/** Manual tester for a JCS instance configured to use the http client. */
-public class RemoteHttpCacheManualTester
-    extends TestCase
-{
-    /** number to use for the test */
-    private static int items = 100;
-
-    /**
-     * Test setup
-     */
-    @Override
-    public void setUp()
-    {
-        JCS.setConfigFilename( "/TestRemoteHttpCache.ccf" );
-    }
-
-    /**
-     * A unit test for JUnit
-     * @throws Exception Description of the Exception
-     */
-    public void testSimpleLoad()
-        throws Exception
-    {
-        CacheAccess<String, String> jcs = JCS.getInstance( "testCache1" );
-
-        jcs.put( "TestKey", "TestValue" );
-
-//        System.out.println( jcs.getStats() );
-
-        for ( int i = 1; i <= items; i++ )
-        {
-            jcs.put( i + ":key", "data" + i );
-        }
-
-        for ( int i = items; i > 0; i-- )
-        {
-            String res = jcs.get( i + ":key" );
-            if ( res == null )
-            {
-                //assertNotNull( "[" + i + ":key] should not be null", res );
-            }
-        }
-
-        // test removal
-        jcs.remove( "300:key" );
-        assertNull( jcs.get( "TestKey" ) );
-
-//        System.out.println( jcs.getStats() );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/http/server/RemoteHttpCacheServiceUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/http/server/RemoteHttpCacheServiceUnitTest.java
deleted file mode 100644
index 6b35897..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/http/server/RemoteHttpCacheServiceUnitTest.java
+++ /dev/null
@@ -1,181 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.http.server;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.auxiliary.MockCacheEventLogger;
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.control.MockCompositeCacheManager;
-
-import java.util.HashSet;
-
-/** Unit tests for the service. */
-public class RemoteHttpCacheServiceUnitTest
-    extends TestCase
-{
-    /**
-     * Verify event log calls.
-     * <p>
-     * @throws Exception
-     */
-    public void testUpdate_simple()
-        throws Exception
-    {
-        // SETUP
-        MockCompositeCacheManager manager = new MockCompositeCacheManager();
-        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
-
-        RemoteHttpCacheServerAttributes rcsa = new RemoteHttpCacheServerAttributes();
-        RemoteHttpCacheService<String, String> server =
-            new RemoteHttpCacheService<>( manager, rcsa, cacheEventLogger );
-
-        String cacheName = "test";
-        String key = "key";
-        long requesterId = 2;
-        CacheElement<String, String> element = new CacheElement<>( cacheName, key, null );
-
-        // DO WORK
-        server.update( element, requesterId );
-
-        // VERIFY
-        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
-        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
-    }
-
-    /**
-     * Verify event log calls.
-     * <p>
-     * @throws Exception
-     */
-    public void testGet_simple()
-        throws Exception
-    {
-        // SETUP
-        MockCompositeCacheManager manager = new MockCompositeCacheManager();
-        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
-
-        RemoteHttpCacheServerAttributes rcsa = new RemoteHttpCacheServerAttributes();
-        RemoteHttpCacheService<String, String> server =
-            new RemoteHttpCacheService<>( manager, rcsa, cacheEventLogger );
-
-        // DO WORK
-        server.get( "region", "key" );
-
-        // VERIFY
-        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
-        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
-    }
-
-    /**
-     * Verify event log calls.
-     * <p>
-     * @throws Exception
-     */
-    public void testGetMatching_simple()
-        throws Exception
-    {
-        // SETUP
-        MockCompositeCacheManager manager = new MockCompositeCacheManager();
-        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
-
-        RemoteHttpCacheServerAttributes rcsa = new RemoteHttpCacheServerAttributes();
-        RemoteHttpCacheService<String, String> server =
-            new RemoteHttpCacheService<>( manager, rcsa, cacheEventLogger );
-
-        // DO WORK
-        server.getMatching( "region", "pattern", 0 );
-
-        // VERIFY
-        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
-        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
-    }
-
-    /**
-     * Verify event log calls.
-     * <p>
-     * @throws Exception
-     */
-    public void testGetMultiple_simple()
-        throws Exception
-    {
-        // SETUP
-        MockCompositeCacheManager manager = new MockCompositeCacheManager();
-        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
-
-        RemoteHttpCacheServerAttributes rcsa = new RemoteHttpCacheServerAttributes();
-        RemoteHttpCacheService<String, String> server =
-            new RemoteHttpCacheService<>( manager, rcsa, cacheEventLogger );
-
-        // DO WORK
-        server.getMultiple( "region", new HashSet<>() );
-
-        // VERIFY
-        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
-        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
-    }
-
-    /**
-     * Verify event log calls.
-     * <p>
-     * @throws Exception
-     */
-    public void testRemove_simple()
-        throws Exception
-    {
-        // SETUP
-        MockCompositeCacheManager manager = new MockCompositeCacheManager();
-        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
-
-        RemoteHttpCacheServerAttributes rcsa = new RemoteHttpCacheServerAttributes();
-        RemoteHttpCacheService<String, String> server =
-            new RemoteHttpCacheService<>( manager, rcsa, cacheEventLogger );
-
-        // DO WORK
-        server.remove( "region", "key" );
-
-        // VERIFY
-        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
-        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
-    }
-
-    /**
-     * Verify event log calls.
-     * <p>
-     * @throws Exception
-     */
-    public void testRemoveAll_simple()
-        throws Exception
-    {
-        // SETUP
-        MockCompositeCacheManager manager = new MockCompositeCacheManager();
-        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
-
-        RemoteHttpCacheServerAttributes rcsa = new RemoteHttpCacheServerAttributes();
-        RemoteHttpCacheService<String, String> server =
-            new RemoteHttpCacheService<>( manager, rcsa, cacheEventLogger );
-
-        // DO WORK
-        server.removeAll( "region" );
-
-        // VERIFY
-        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
-        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/http/server/RemoteHttpCacheServletUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/http/server/RemoteHttpCacheServletUnitTest.java
deleted file mode 100644
index 93cb7c9..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/http/server/RemoteHttpCacheServletUnitTest.java
+++ /dev/null
@@ -1,177 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.http.server;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.Serializable;
-import java.util.Collections;
-import java.util.Set;
-
-import junit.framework.TestCase;
-
-import org.apache.commons.jcs.auxiliary.remote.MockRemoteCacheService;
-import org.apache.commons.jcs.auxiliary.remote.util.RemoteCacheRequestFactory;
-import org.apache.commons.jcs.auxiliary.remote.value.RemoteCacheRequest;
-import org.apache.commons.jcs.auxiliary.remote.value.RemoteCacheResponse;
-import org.apache.commons.jcs.engine.CacheElement;
-
-/** Unit tests for the servlet. */
-public class RemoteHttpCacheServletUnitTest
-    extends TestCase
-{
-    private RemoteHttpCacheServlet servlet;
-    private MockRemoteCacheService<Serializable, Serializable> remoteHttpCacheService;
-
-    /**
-     * @see junit.framework.TestCase#setUp()
-     */
-    @Override
-    protected void setUp() throws Exception
-    {
-        super.setUp();
-        servlet = new RemoteHttpCacheServlet();
-        servlet.init(null);
-
-        remoteHttpCacheService = new MockRemoteCacheService<>();
-        servlet.setRemoteCacheService( remoteHttpCacheService );
-    }
-
-    /**
-     * @see junit.framework.TestCase#tearDown()
-     */
-    @Override
-    protected void tearDown() throws Exception
-    {
-        servlet.destroy();
-        super.tearDown();
-    }
-
-    /** Verify that we balk and return an error. */
-    public void testProcessRequest_null()
-    {
-        RemoteCacheRequest<Serializable, Serializable> request = null;
-
-        // DO WORK
-        RemoteCacheResponse<Object> result = servlet.processRequest( request );
-
-        // VERIFY
-        assertNotNull( "Should have a result.", result );
-        assertTrue( "Should have 'The request is null' in the errorMessage", result.getErrorMessage().indexOf( "The request is null" ) != -1 );
-        assertTrue( "Should have 'The request is null' in the toString", result.toString().indexOf( "The request is null" ) != -1 );
-    }
-
-    /** Verify that the service is called. */
-    public void testProcessRequest_Get()
-    {
-        String cacheName = "test";
-        Serializable key = "key";
-        long requesterId = 2;
-        RemoteCacheRequest<Serializable, Serializable> request = RemoteCacheRequestFactory.createGetRequest( cacheName, key, requesterId );
-
-        // DO WORK
-        RemoteCacheResponse<Object> result = servlet.processRequest( request );
-
-        // VERIFY
-        assertNotNull( "Should have a result.", result );
-        assertEquals( "Wrong key.", key, remoteHttpCacheService.lastGetKey );
-    }
-
-    /** Verify that the service is called. */
-    public void testProcessRequest_GetMatching()
-    {
-        String cacheName = "test";
-        String pattern = "pattern";
-        long requesterId = 2;
-        RemoteCacheRequest<Serializable, Serializable> request = RemoteCacheRequestFactory.createGetMatchingRequest( cacheName, pattern,
-                                                                                                  requesterId );
-
-        // DO WORK
-        RemoteCacheResponse<Object> result = servlet.processRequest( request );
-
-        // VERIFY
-        assertNotNull( "Should have a result.", result );
-        assertEquals( "Wrong pattern.", pattern, remoteHttpCacheService.lastGetMatchingPattern );
-    }
-
-    /** Verify that the service is called. */
-    public void testProcessRequest_GetMultiple()
-    {
-        String cacheName = "test";
-        Set<Serializable> keys = Collections.emptySet();
-        long requesterId = 2;
-        RemoteCacheRequest<Serializable, Serializable> request = RemoteCacheRequestFactory.createGetMultipleRequest( cacheName, keys,
-                                                                                                  requesterId );
-
-        // DO WORK
-        RemoteCacheResponse<Object> result = servlet.processRequest( request );
-
-        // VERIFY
-        assertNotNull( "Should have a result.", result );
-        assertEquals( "Wrong keys.", keys, remoteHttpCacheService.lastGetMultipleKeys );
-
-    }
-
-    /** Verify that the service is called. */
-    public void testProcessRequest_Update()
-    {
-        String cacheName = "test";
-        String key = "key";
-        long requesterId = 2;
-        CacheElement<Serializable, Serializable> element = new CacheElement<>( cacheName, key, null );
-        RemoteCacheRequest<Serializable, Serializable> request = RemoteCacheRequestFactory.createUpdateRequest( element, requesterId );
-
-        // DO WORK
-        RemoteCacheResponse<Object> result = servlet.processRequest( request );
-
-        // VERIFY
-        assertNotNull( "Should have a result.", result );
-        assertEquals( "Wrong object.", element, remoteHttpCacheService.lastUpdate );
-    }
-
-    /** Verify that the service is called. */
-    public void testProcessRequest_Remove()
-    {
-        String cacheName = "test";
-        Serializable key = "key";
-        long requesterId = 2;
-        RemoteCacheRequest<Serializable, Serializable> request = RemoteCacheRequestFactory.createRemoveRequest( cacheName, key, requesterId );
-
-        // DO WORK
-        RemoteCacheResponse<Object> result = servlet.processRequest( request );
-
-        // VERIFY
-        assertNotNull( "Should have a result.", result );
-        assertEquals( "Wrong key.", key, remoteHttpCacheService.lastRemoveKey );
-    }
-
-    /** Verify that the service is called. */
-    public void testProcessRequest_RemoveAll()
-    {
-        String cacheName = "testRemoveALl";
-        long requesterId = 2;
-        RemoteCacheRequest<Serializable, Serializable> request = RemoteCacheRequestFactory.createRemoveAllRequest( cacheName, requesterId );
-
-        // DO WORK
-        RemoteCacheResponse<Object> result = servlet.processRequest( request );
-
-        // VERIFY
-        assertNotNull( "Should have a result.", result );
-        assertEquals( "Wrong cacheName.", cacheName, remoteHttpCacheService.lastRemoveAllCacheName );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/server/BasicRemoteCacheClientServerUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/server/BasicRemoteCacheClientServerUnitTest.java
deleted file mode 100644
index 21ea4dd..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/server/BasicRemoteCacheClientServerUnitTest.java
+++ /dev/null
@@ -1,341 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.server;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.NetworkInterface;
-import java.util.Enumeration;
-
-import org.apache.commons.jcs.auxiliary.AuxiliaryCache;
-import org.apache.commons.jcs.auxiliary.MockCacheEventLogger;
-import org.apache.commons.jcs.auxiliary.remote.MockRemoteCacheListener;
-import org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes;
-import org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory;
-import org.apache.commons.jcs.auxiliary.remote.RemoteCacheManager;
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.CacheStatus;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.control.MockCompositeCacheManager;
-import org.apache.commons.jcs.engine.control.MockElementSerializer;
-import org.apache.commons.jcs.utils.net.HostNameUtil;
-import org.apache.commons.jcs.utils.timing.SleepUtil;
-import org.junit.AfterClass;
-import org.junit.Assert;
-import org.junit.BeforeClass;
-import org.junit.FixMethodOrder;
-import org.junit.Test;
-import org.junit.runners.MethodSorters;
-
-/**
- * These tests startup the remote server and make requests to it.
- * <p>
- *
- * @author Aaron Smuts
- */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class BasicRemoteCacheClientServerUnitTest extends Assert
-{
-    private static final int LOCAL_PORT = 12020;
-
-   /**
-     * Server instance to use in the tests.
-     */
-    private static RemoteCacheServer<String, String> server = null;
-
-    /**
-     * Factory instance to use in the tests.
-     */
-    private static RemoteCacheFactory factory = null;
-
-    /**
-     * the remote server port
-     */
-    private static int remotePort;
-
-    /**
-     * Starts the server. This is not in a setup, since the server is slow to kill right now.
-     */
-    @BeforeClass
-    public static void setup()
-    {
-        // Add some debug to try and find out why test fails on Jenkins/Continuum
-        try {
-            InetAddress lh = InetAddress.getByName("localhost");
-            System.out.println("localhost="+lh);
-            InetAddress ina=InetAddress.getLocalHost();
-            System.out.println("InetAddress.getLocalHost()="+ina);
-            // Iterate all NICs (network interface cards)...
-            for ( Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces(); ifaces.hasMoreElements(); )
-            {
-                NetworkInterface iface = ifaces.nextElement();
-                // Iterate all IP addresses assigned to each card...
-                for ( Enumeration<InetAddress> inetAddrs = iface.getInetAddresses(); inetAddrs.hasMoreElements(); )
-                {
-                    InetAddress inetAddr = inetAddrs.nextElement();
-                    boolean loopbackAddress = inetAddr.isLoopbackAddress();
-                    boolean siteLocalAddress = inetAddr.isSiteLocalAddress();
-                    System.out.println("Found: "+ inetAddr +
-                            " isLoopback: " + loopbackAddress +
-                            " isSiteLocal: " + siteLocalAddress +
-                            ((!loopbackAddress && siteLocalAddress) ? " *" : ""));
-                }
-            }
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-        // end of debug
-        String configFile = "TestRemoteCacheClientServer.ccf";
-        server = RemoteCacheServerStartupUtil.startServerUsingProperties(configFile);
-        factory = new RemoteCacheFactory();
-        factory.initialize();
-        remotePort = server.remoteCacheServerAttributes.getRemoteLocation().getPort();
-    }
-
-    @AfterClass
-    public static void stop() throws IOException
-    {
-        if (server != null) { // in case setup failed, no point throwing NPE as well
-            server.shutdown("localhost", remotePort);
-        }
-        // Debug: unfortunately Surefire restarts JVM so log files get overwritten
-        // There's probably a better way to fix this ...
-        java.io.File jcsLog = new java.io.File("target/jcs.log");
-        java.io.File logSave = new java.io.File("target/BasicRemoteCacheClientServerUnitTest_jcs.log");
-        System.out.println("Renamed log file? "+jcsLog.renameTo(logSave));
-    }
-
-    /**
-     * Verify that we can start the remote cache server. Send an item to the remote. Verify that the
-     * remote put count goes up. If we go through JCS, the manager will be shared and we will get
-     * into an endless loop. We will use a mock cache manager instead.
-     * <p>
-     * The remote server uses the real JCS. We can verify that items are added to JCS behind the
-     * server by calling get. We cannot access it directly via JCS since it is serialized.
-     * <p>
-     * This test uses a mock injected client to test a normal server.
-     * <p>
-     *
-     * @throws Exception
-     */
-    @Test
-    public void test1SinglePut()
-            throws Exception
-            {
-        // SETUP
-        MockCompositeCacheManager compositeCacheManager = new MockCompositeCacheManager();
-
-        RemoteCacheAttributes attributes = new RemoteCacheAttributes();
-        attributes.setRemoteLocation("localhost", remotePort);
-        attributes.setLocalPort(LOCAL_PORT);
-        attributes.setCacheName("testSinglePut");
-
-        RemoteCacheManager remoteCacheManager = factory.getManager(attributes, compositeCacheManager, new MockCacheEventLogger(), new MockElementSerializer());
-        AuxiliaryCache<String, String> cache = remoteCacheManager.getCache(attributes);
-
-        // DO WORK
-        int numPutsPrior = server.getPutCount();
-        ICacheElement<String, String> element = new CacheElement<>(cache.getCacheName(), "key", "value");
-        cache.update(element);
-        SleepUtil.sleepAtLeast(200);
-
-        // VERIFY
-        try
-        {
-            assertEquals("Cache is alive", CacheStatus.ALIVE, cache.getStatus());
-            assertEquals("Wrong number of puts", 1, server.getPutCount() - numPutsPrior);
-        }
-        catch (junit.framework.AssertionFailedError e)
-        {
-            System.out.println(cache.getStats());
-            System.out.println(server.getStats());
-            throw e;
-        }
-
-        // DO WORK
-        ICacheElement<String, String> result = cache.get("key");
-
-        // VERIFY
-        assertEquals("Wrong element.", element.getVal(), result.getVal());
-            }
-
-    /**
-     * Verify that we can remove an item via the remote server.
-     * <p>
-     *
-     * @throws Exception
-     */
-    @Test
-    public void test2PutRemove()
-            throws Exception
-            {
-        // SETUP
-        MockCompositeCacheManager compositeCacheManager = new MockCompositeCacheManager();
-
-        RemoteCacheAttributes attributes = new RemoteCacheAttributes();
-        attributes.setRemoteLocation("localhost", remotePort);
-        attributes.setLocalPort(LOCAL_PORT);
-        attributes.setCacheName("testPutRemove");
-
-        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
-
-        RemoteCacheManager remoteCacheManager = factory.getManager(attributes, compositeCacheManager, cacheEventLogger, null);
-        AuxiliaryCache<String, String> cache = remoteCacheManager.getCache(attributes);
-
-        // DO WORK
-        int numPutsPrior = server.getPutCount();
-        ICacheElement<String, String> element = new CacheElement<>(cache.getCacheName(), "key", "value");
-        cache.update(element);
-        SleepUtil.sleepAtLeast(50);
-
-        // VERIFY
-        try
-        {
-            assertEquals("Cache is alive", CacheStatus.ALIVE, cache.getStatus());
-            assertEquals("Wrong number of puts", 1, server.getPutCount() - numPutsPrior);
-        }
-        catch (junit.framework.AssertionFailedError e)
-        {
-            System.out.println(cache.getStats());
-            System.out.println(server.getStats());
-            throw e;
-        }
-
-        // DO WORK
-        ICacheElement<String, String> result = cache.get("key");
-
-        // VERIFY
-        assertEquals("Wrong element.", element.getVal(), result.getVal());
-
-        // DO WORK
-        cache.remove("key");
-        SleepUtil.sleepAtLeast(200);
-        ICacheElement<String, String> resultAfterRemote = cache.get("key");
-
-        // VERIFY
-        assertNull("Element resultAfterRemote should be null.", resultAfterRemote);
-            }
-
-    /**
-     * Register a listener with the server. Send an update. Verify that the listener received it.
-     *
-     * @throws Exception
-     */
-    @Test
-    public void test3PutAndListen()
-            throws Exception
-            {
-        // SETUP
-        MockCompositeCacheManager compositeCacheManager = new MockCompositeCacheManager();
-
-        RemoteCacheAttributes attributes = new RemoteCacheAttributes();
-        attributes.setRemoteLocation("localhost", remotePort);
-        attributes.setLocalPort(LOCAL_PORT);
-        attributes.setCacheName("testPutAndListen");
-
-        RemoteCacheManager remoteCacheManager = factory.getManager(attributes, compositeCacheManager, new MockCacheEventLogger(), new MockElementSerializer());
-        AuxiliaryCache<String, String> cache = remoteCacheManager.getCache(attributes);
-
-        MockRemoteCacheListener<String, String> listener = new MockRemoteCacheListener<>();
-        server.addCacheListener(cache.getCacheName(), listener);
-
-        // DO WORK
-        int numPutsPrior = server.getPutCount();
-        ICacheElement<String, String> element = new CacheElement<>(cache.getCacheName(), "key", "value");
-        cache.update(element);
-        SleepUtil.sleepAtLeast(50);
-
-        // VERIFY
-        try
-        {
-            assertEquals("Cache is alive", CacheStatus.ALIVE, cache.getStatus());
-            assertEquals("Wrong number of puts", 1, server.getPutCount() - numPutsPrior);
-            assertEquals("Wrong number of puts to listener.", 1, listener.putCount);
-        }
-        catch (junit.framework.AssertionFailedError e)
-        {
-            System.out.println(cache.getStats());
-            System.out.println(server.getStats());
-            throw e;
-        }
-        finally
-        {
-            // remove from all regions.
-            server.removeCacheListener(listener);
-        }
-    }
-
-    /**
-     * Register a listener with the server. Send an update. Verify that the listener received it.
-     *
-     * @throws Exception
-     */
-    @Test
-    public void test4PutaMultipleAndListen()
-            throws Exception
-    {
-        // SETUP
-        MockCompositeCacheManager compositeCacheManager = new MockCompositeCacheManager();
-
-        RemoteCacheAttributes attributes = new RemoteCacheAttributes();
-        attributes.setRemoteLocation("localhost", remotePort);
-        attributes.setLocalPort(LOCAL_PORT);
-        attributes.setCacheName("testPutaMultipleAndListen");
-
-        RemoteCacheManager remoteCacheManager = factory.getManager(attributes, compositeCacheManager, new MockCacheEventLogger(), new MockElementSerializer());
-        AuxiliaryCache<String, String> cache = remoteCacheManager.getCache(attributes);
-
-        MockRemoteCacheListener<String, String> listener = new MockRemoteCacheListener<>();
-        server.addCacheListener(cache.getCacheName(), listener);
-
-        // DO WORK
-        int numPutsPrior = server.getPutCount();
-        int numToPut = 100;
-        for (int i = 0; i < numToPut; i++)
-        {
-            ICacheElement<String, String> element = new CacheElement<>(cache.getCacheName(), "key" + 1, "value" + i);
-            cache.update(element);
-        }
-        SleepUtil.sleepAtLeast(500);
-
-        // VERIFY
-        try
-        {
-            assertEquals("Cache is alive", CacheStatus.ALIVE, cache.getStatus());
-            assertEquals("Wrong number of puts", numToPut, server.getPutCount() - numPutsPrior);
-            assertEquals("Wrong number of puts to listener.", numToPut, listener.putCount);
-        }
-        catch (junit.framework.AssertionFailedError e)
-        {
-            System.out.println(cache.getStats());
-            System.out.println(server.getStats());
-            throw e;
-        }
-    }
-
-    @Test
-    public void testLocalHost() throws Exception
-    {
-        final InetAddress byName = InetAddress.getByName("localhost");
-        assertTrue("Expected localhost (" + byName.getHostAddress() + ") to be a loopback address", byName.isLoopbackAddress());
-        final InetAddress localHost = HostNameUtil.getLocalHostLANAddress();
-        assertTrue("Expected getLocalHostLANAddress() (" + localHost + ") to return a site local address", localHost.isSiteLocalAddress());
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/server/MockRMISocketFactory.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/server/MockRMISocketFactory.java
deleted file mode 100644
index e007753..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/server/MockRMISocketFactory.java
+++ /dev/null
@@ -1,88 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.server;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.io.Serializable;
-import java.net.InetSocketAddress;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.rmi.server.RMISocketFactory;
-
-/** For testing the custom socket factory configuration */
-public class MockRMISocketFactory
-    extends RMISocketFactory
-    implements Serializable
-{
-    /** Don't change */
-    private static final long serialVersionUID = 1056199478581218676L;
-
-    /** for testing automatic property configuration. */
-    private String testStringProperty;
-
-    /**
-     * @param host
-     * @param port
-     * @return Socket
-     * @throws IOException
-     */
-    @Override
-    public Socket createSocket( String host, int port )
-        throws IOException
-    {
-//        System.out.println( "Creating socket" );
-
-        Socket socket = new Socket();
-        socket.setSoTimeout( 1000 );
-        socket.setSoLinger( false, 0 );
-        socket.connect( new InetSocketAddress( host, port ), 1000 );
-        return socket;
-    }
-
-    /**
-     * @param port
-     * @return ServerSocket
-     * @throws IOException
-     */
-    @Override
-    public ServerSocket createServerSocket( int port )
-        throws IOException
-    {
-//        System.out.println( "Creating server socket" );
-
-        return new ServerSocket( port );
-    }
-
-    /**
-     * @param testStringProperty the testStringProperty to set
-     */
-    public void setTestStringProperty( String testStringProperty )
-    {
-        this.testStringProperty = testStringProperty;
-    }
-
-    /**
-     * @return the testStringProperty
-     */
-    public String getTestStringProperty()
-    {
-        return testStringProperty;
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/server/RegistryKeepAliveRunnerUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/server/RegistryKeepAliveRunnerUnitTest.java
deleted file mode 100644
index ba07886..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/server/RegistryKeepAliveRunnerUnitTest.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.server;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.auxiliary.MockCacheEventLogger;
-
-/** Unit tests for the registry keep alive runner. */
-public class RegistryKeepAliveRunnerUnitTest
-    extends TestCase
-{
-    /** Verify that we get the appropriate event log */
-    public void testCheckAndRestoreIfNeeded_failure()
-    {
-        // SETUP
-        String host = "localhost";
-        int port = 1234;
-        String service = "doesn'texist";
-        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
-
-        RegistryKeepAliveRunner runner = new RegistryKeepAliveRunner( host, port, service );
-        runner.setCacheEventLogger( cacheEventLogger );
-
-        // DO WORK
-        runner.checkAndRestoreIfNeeded();
-
-        // VERIFY
-        // 1 for the lookup, one for the rebind since the server isn't created yet
-        assertEquals( "error tally", 2, cacheEventLogger.errorEventCalls );
-        //System.out.println( cacheEventLogger.errorMessages );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/server/RemoteCacheServerAttributesUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/server/RemoteCacheServerAttributesUnitTest.java
deleted file mode 100644
index 535f130..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/server/RemoteCacheServerAttributesUnitTest.java
+++ /dev/null
@@ -1,64 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.server;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.auxiliary.remote.server.behavior.RemoteType;
-
-/**
- * Tests for the remote cache server attributes.
- * <p>
- * @author Aaron Smuts
- */
-public class RemoteCacheServerAttributesUnitTest
-    extends TestCase
-{
-
-    /**
-     * Verify that we get a string, even if not attributes are set.
-     */
-    public void testToString()
-    {
-        RemoteCacheServerAttributes attributes = new RemoteCacheServerAttributes();
-        assertNotNull( "Should have a string.", attributes.toString() );
-    }
-
-    /**
-     * Verify that the type is set correctly and that the correct name is returned for the type.
-     */
-    public void testSetRemoteTypeName_local()
-    {
-        RemoteCacheServerAttributes attributes = new RemoteCacheServerAttributes();
-        attributes.setRemoteTypeName( "LOCAL" );
-        assertEquals( "Wrong type.", RemoteType.LOCAL, attributes.getRemoteType() );
-        assertEquals( "Wrong name", "LOCAL", attributes.getRemoteTypeName() );
-    }
-
-    /**
-     * Verify that the type is set correctly and that the correct name is returned for the type.
-     */
-    public void testSetRemoteTypeName_cluster()
-    {
-        RemoteCacheServerAttributes attributes = new RemoteCacheServerAttributes();
-        attributes.setRemoteTypeName( "CLUSTER" );
-        assertEquals( "Wrong type.", RemoteType.CLUSTER, attributes.getRemoteType() );
-        assertEquals( "Wrong name", "CLUSTER", attributes.getRemoteTypeName() );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/server/RemoteCacheServerFactoryUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/server/RemoteCacheServerFactoryUnitTest.java
deleted file mode 100644
index 22ef757..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/server/RemoteCacheServerFactoryUnitTest.java
+++ /dev/null
@@ -1,202 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.server;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.auxiliary.remote.behavior.ICommonRemoteCacheAttributes;
-import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheConstants;
-
-import java.rmi.server.RMISocketFactory;
-import java.util.Properties;
-
-/** Unit tests for the factory */
-public class RemoteCacheServerFactoryUnitTest
-    extends TestCase
-{
-    /** verify that we get the timeout value */
-    public void testConfigureRemoteCacheServerAttributes_eventQueuePoolName()
-    {
-        // SETUP
-        String eventQueuePoolName = "specialName";
-        Properties props = new Properties();
-        props.put( IRemoteCacheConstants.CACHE_SERVER_ATTRIBUTES_PROPERTY_PREFIX + ".EventQueuePoolName", eventQueuePoolName );
-
-        // DO WORK
-        RemoteCacheServerAttributes result = RemoteCacheServerFactory.configureRemoteCacheServerAttributes( props );
-
-        // VERIFY
-        assertEquals( "Wrong eventQueuePoolName", eventQueuePoolName, result.getEventQueuePoolName() );
-    }
-
-    /** verify that we get the timeout value */
-    public void testConfigureRemoteCacheServerAttributes_timeoutPresent()
-    {
-        // SETUP
-        int timeout = 123245;
-        Properties props = new Properties();
-        props.put( IRemoteCacheConstants.SOCKET_TIMEOUT_MILLIS, String.valueOf( timeout ) );
-
-        // DO WORK
-        RemoteCacheServerAttributes result = RemoteCacheServerFactory.configureRemoteCacheServerAttributes( props );
-
-        // VERIFY
-        assertEquals( "Wrong timeout", timeout, result.getRmiSocketFactoryTimeoutMillis() );
-    }
-
-    /** verify that we get the timeout value */
-    public void testConfigureRemoteCacheServerAttributes_timeoutNotPresent()
-    {
-        // SETUP
-        Properties props = new Properties();
-
-        // DO WORK
-        RemoteCacheServerAttributes result = RemoteCacheServerFactory.configureRemoteCacheServerAttributes( props );
-
-        // VERIFY
-        assertEquals( "Wrong timeout", ICommonRemoteCacheAttributes.DEFAULT_RMI_SOCKET_FACTORY_TIMEOUT_MILLIS, result.getRmiSocketFactoryTimeoutMillis() );
-    }
-
-    /** verify that we get the registryKeepAliveDelayMillis value */
-    public void testConfigureRemoteCacheServerAttributes_registryKeepAliveDelayMillisPresent()
-    {
-        // SETUP
-        int registryKeepAliveDelayMillis = 123245;
-        Properties props = new Properties();
-        props.put( IRemoteCacheConstants.CACHE_SERVER_ATTRIBUTES_PROPERTY_PREFIX + ".registryKeepAliveDelayMillis", String.valueOf( registryKeepAliveDelayMillis ) );
-
-        // DO WORK
-        RemoteCacheServerAttributes result = RemoteCacheServerFactory.configureRemoteCacheServerAttributes( props );
-
-        // VERIFY
-        assertEquals( "Wrong registryKeepAliveDelayMillis", registryKeepAliveDelayMillis, result.getRegistryKeepAliveDelayMillis() );
-    }
-
-    /** verify that we get the useRegistryKeepAlive value */
-    public void testConfigureRemoteCacheServerAttributes_useRegistryKeepAlivePresent()
-    {
-        // SETUP
-        boolean useRegistryKeepAlive = false;
-        Properties props = new Properties();
-        props.put( IRemoteCacheConstants.CACHE_SERVER_ATTRIBUTES_PROPERTY_PREFIX + ".useRegistryKeepAlive", String.valueOf( useRegistryKeepAlive ) );
-
-        // DO WORK
-        RemoteCacheServerAttributes result = RemoteCacheServerFactory.configureRemoteCacheServerAttributes( props );
-
-        // VERIFY
-        assertEquals( "Wrong useRegistryKeepAlive", useRegistryKeepAlive, result.isUseRegistryKeepAlive() );
-    }
-
-    /** verify that we get the startRegistry value */
-    public void testConfigureRemoteCacheServerAttributes_startRegistryPresent()
-    {
-        // SETUP
-        boolean startRegistry = false;
-        Properties props = new Properties();
-        props.put( IRemoteCacheConstants.CACHE_SERVER_ATTRIBUTES_PROPERTY_PREFIX + ".startRegistry", String.valueOf( startRegistry ) );
-
-        // DO WORK
-        RemoteCacheServerAttributes result = RemoteCacheServerFactory.configureRemoteCacheServerAttributes( props );
-
-        // VERIFY
-        assertEquals( "Wrong startRegistry", startRegistry, result.isStartRegistry() );
-    }
-
-    /** verify that we get the registryKeepAliveDelayMillis value */
-    public void testConfigureRemoteCacheServerAttributes_rmiSocketFactoryTimeoutMillisPresent()
-    {
-        // SETUP
-        int rmiSocketFactoryTimeoutMillis = 123245;
-        Properties props = new Properties();
-        props.put( IRemoteCacheConstants.CACHE_SERVER_ATTRIBUTES_PROPERTY_PREFIX + ".rmiSocketFactoryTimeoutMillis", String.valueOf( rmiSocketFactoryTimeoutMillis ) );
-
-        // DO WORK
-        RemoteCacheServerAttributes result = RemoteCacheServerFactory.configureRemoteCacheServerAttributes( props );
-
-        // VERIFY
-        assertEquals( "Wrong rmiSocketFactoryTimeoutMillis", rmiSocketFactoryTimeoutMillis, result.getRmiSocketFactoryTimeoutMillis() );
-    }
-
-    /** verify that we get the startRegistry value */
-    public void testConfigureRemoteCacheServerAttributes_allowClusterGetPresent()
-    {
-        // SETUP
-        boolean allowClusterGet = false;
-        Properties props = new Properties();
-        props.put( IRemoteCacheConstants.CACHE_SERVER_ATTRIBUTES_PROPERTY_PREFIX + ".allowClusterGet", String.valueOf( allowClusterGet ) );
-
-        // DO WORK
-        RemoteCacheServerAttributes result = RemoteCacheServerFactory.configureRemoteCacheServerAttributes( props );
-
-        // VERIFY
-        assertEquals( "Wrong allowClusterGet", allowClusterGet, result.isAllowClusterGet() );
-    }
-
-    /** verify that we get the startRegistry value */
-    public void testConfigureRemoteCacheServerAttributes_localClusterConsistencyPresent()
-    {
-        // SETUP
-        boolean localClusterConsistency = false;
-        Properties props = new Properties();
-        props.put( IRemoteCacheConstants.CACHE_SERVER_ATTRIBUTES_PROPERTY_PREFIX + ".localClusterConsistency", String.valueOf( localClusterConsistency ) );
-
-        // DO WORK
-        RemoteCacheServerAttributes result = RemoteCacheServerFactory.configureRemoteCacheServerAttributes( props );
-
-        // VERIFY
-        assertEquals( "Wrong localClusterConsistency", localClusterConsistency, result.isLocalClusterConsistency() );
-    }
-
-    /** verify that we get the timeout value */
-    public void testConfigureObjectSpecificCustomFactory_withProperty()
-    {
-        // SETUP
-        String testValue = "123245";
-        Properties props = new Properties();
-        props.put( IRemoteCacheConstants.CUSTOM_RMI_SOCKET_FACTORY_PROPERTY_PREFIX, MockRMISocketFactory.class.getName() );
-        props.put( IRemoteCacheConstants.CUSTOM_RMI_SOCKET_FACTORY_PROPERTY_PREFIX + ".testStringProperty", testValue );
-
-        // DO WORK
-        RMISocketFactory result = RemoteCacheServerFactory.configureObjectSpecificCustomFactory( props );
-
-        // VERIFY
-        assertNotNull( "Should have a custom socket factory.", result );
-        assertEquals( "Wrong testValue", testValue, ((MockRMISocketFactory)result).getTestStringProperty() );
-    }
-
-    /** verify that we get the timeout value */
-    public void testConfigureObjectSpecificCustomFactory_withProperty_TimeoutConfigurableRMIScoketFactory()
-    {
-        // SETUP
-        int readTimeout = 1234;
-        int openTimeout = 1234;
-        Properties props = new Properties();
-        props.put( IRemoteCacheConstants.CUSTOM_RMI_SOCKET_FACTORY_PROPERTY_PREFIX, TimeoutConfigurableRMISocketFactory.class.getName() );
-        props.put( IRemoteCacheConstants.CUSTOM_RMI_SOCKET_FACTORY_PROPERTY_PREFIX + ".readTimeout", String.valueOf( readTimeout ) );
-        props.put( IRemoteCacheConstants.CUSTOM_RMI_SOCKET_FACTORY_PROPERTY_PREFIX + ".openTimeout", String.valueOf( openTimeout ) );
-
-        // DO WORK
-        RMISocketFactory result = RemoteCacheServerFactory.configureObjectSpecificCustomFactory( props );
-
-        // VERIFY
-        assertNotNull( "Should have a custom socket factory.", result );
-        assertEquals( "Wrong readTimeout", readTimeout, ((TimeoutConfigurableRMISocketFactory)result).getReadTimeout() );
-        assertEquals( "Wrong readTimeout", openTimeout, ((TimeoutConfigurableRMISocketFactory)result).getOpenTimeout() );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/server/RemoteCacheServerStartupUtil.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/server/RemoteCacheServerStartupUtil.java
deleted file mode 100644
index 03a79c1..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/server/RemoteCacheServerStartupUtil.java
+++ /dev/null
@@ -1,112 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.server;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.net.UnknownHostException;
-import java.util.Properties;
-
-import org.apache.commons.jcs.auxiliary.remote.RemoteUtils;
-import org.apache.commons.jcs.utils.net.HostNameUtil;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- *Starts the registry and runs the server via the factory.
- *<p>
- * @author Aaron Smuts
- */
-public class RemoteCacheServerStartupUtil
-{
-    /** The logger */
-    private static final Log log = LogManager.getLog( RemoteCacheServerStartupUtil.class );
-
-    /** Registry to use in the test. */
-    private static final int DEFAULT_REGISTRY_PORT = 1101;
-
-    /**
-     * Starts the registry on port "registry.port"
-     * <p>
-     * @param propsFileName
-     * @return RemoteCacheServer
-     */
-    public static <K, V> RemoteCacheServer<K, V> startServerUsingProperties( String propsFileName )
-    {
-        // TODO load from props file or get as init param or get from jndi, or
-        // all three
-        int registryPort = DEFAULT_REGISTRY_PORT;
-
-        Properties props = null;
-        try
-        {
-            props = RemoteUtils.loadProps(propsFileName);
-        }
-        catch (IOException e)
-        {
-            log.error( "Problem loading configuration from " + propsFileName, e);
-        }
-        
-        if ( props != null )
-        {
-            String portS = props.getProperty( "registry.port", String.valueOf( DEFAULT_REGISTRY_PORT ) );
-
-            try
-            {
-                registryPort = Integer.parseInt( portS );
-            }
-            catch ( NumberFormatException e )
-            {
-                log.error( "Problem converting port to an int.", e );
-            }
-        }
-
-        // we will always use the local machine for the registry
-        try
-        {
-            String registryHost = HostNameUtil.getLocalHostAddress();
-
-            if ( log.isDebugEnabled() )
-            {
-                log.debug( "registryHost = [" + registryHost + "]" );
-            }
-
-            if ( "localhost".equals( registryHost ) || "127.0.0.1".equals( registryHost ) )
-            {
-                log.warn( "The local address [" + registryHost
-                    + "] is INVALID.  Other machines must be able to use the address to reach this server." );
-            }
-
-            try
-            {
-                RemoteCacheServerFactory.startup( registryHost, registryPort, props );
-            }
-            catch ( IOException e )
-            {
-                log.error( "Problem starting remote cache server.", e );
-            }
-        }
-        catch ( UnknownHostException e )
-        {
-            log.error( "Could not get local address to use for the registry!", e );
-        }
-
-        return RemoteCacheServerFactory.getRemoteCacheServer();
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/server/RemoteCacheServerUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/server/RemoteCacheServerUnitTest.java
deleted file mode 100644
index d002f80..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/server/RemoteCacheServerUnitTest.java
+++ /dev/null
@@ -1,465 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.server;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Properties;
-
-import org.apache.commons.jcs.auxiliary.MockCacheEventLogger;
-import org.apache.commons.jcs.auxiliary.remote.MockRemoteCacheListener;
-import org.apache.commons.jcs.auxiliary.remote.RemoteUtils;
-import org.apache.commons.jcs.auxiliary.remote.server.behavior.IRemoteCacheServerAttributes;
-import org.apache.commons.jcs.auxiliary.remote.server.behavior.RemoteType;
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.utils.timing.SleepUtil;
-
-import junit.framework.TestCase;
-
-/**
- * Since the server does not know that it is a server, it is easy to unit test. The factory does all
- * the rmi work.
- * <p>
- * @author Aaron Smuts
- */
-public class RemoteCacheServerUnitTest
-    extends TestCase
-{
-    private static final String expectedIp1 = "adfasdf";
-    private static final String expectedIp2 = "adsfadsafaf";
-
-    private RemoteCacheServer<String, String> server;
-
-    @Override
-    protected void setUp() throws Exception
-    {
-        super.setUp();
-
-        IRemoteCacheServerAttributes rcsa = new RemoteCacheServerAttributes();
-        rcsa.setConfigFileName( "/TestRemoteCacheServer.ccf" );
-        Properties config = RemoteUtils.loadProps(rcsa.getConfigFileName());
-        this.server = new RemoteCacheServer<>( rcsa, config );
-    }
-
-    @Override
-    protected void tearDown() throws Exception
-    {
-        this.server.shutdown();
-
-        super.tearDown();
-    }
-
-    /**
-     * Add a listener. Pass the id of 0, verify that the server sets a new listener id. Do another
-     * and verify that the second gets an id of 2.
-     * <p>
-     * @throws Exception
-     */
-    public void testAddListenerToCache_LOCALtype()
-        throws Exception
-    {
-        MockRemoteCacheListener<String, String> mockListener1 = new MockRemoteCacheListener<>();
-        mockListener1.remoteType = RemoteType.LOCAL;
-        mockListener1.localAddress = expectedIp1;
-        MockRemoteCacheListener<String, String> mockListener2 = new MockRemoteCacheListener<>();
-        mockListener1.remoteType = RemoteType.LOCAL;
-        mockListener2.localAddress = expectedIp2;
-
-        String cacheName = "testAddListener";
-
-        // DO WORK
-        server.addCacheListener( cacheName, mockListener1 );
-        server.addCacheListener( cacheName, mockListener2 );
-
-        // VERIFY
-        assertEquals( "Wrong listener id.", 1, mockListener1.getListenerId() );
-        assertEquals( "Wrong listener id.", 2, mockListener2.getListenerId() );
-        assertEquals( "Wrong ip.", expectedIp1, server.getExtraInfoForRequesterId( 1 ) );
-        assertEquals( "Wrong ip.", expectedIp2, server.getExtraInfoForRequesterId( 2 ) );
-    }
-
-    /**
-     * Add a listener. Pass the id of 0, verify that the server sets a new listener id. Do another
-     * and verify that the second gets an id of 2.
-     * <p>
-     * @throws Exception
-     */
-    public void testAddListenerToCache_CLUSTERtype()
-        throws Exception
-    {
-        MockRemoteCacheListener<String, String> mockListener1 = new MockRemoteCacheListener<>();
-        mockListener1.remoteType = RemoteType.CLUSTER;
-        mockListener1.localAddress = expectedIp1;
-        MockRemoteCacheListener<String, String> mockListener2 = new MockRemoteCacheListener<>();
-        mockListener1.remoteType = RemoteType.CLUSTER;
-        mockListener2.localAddress = expectedIp2;
-
-        String cacheName = "testAddListener";
-
-        // DO WORK
-        server.addCacheListener( cacheName, mockListener1 );
-        server.addCacheListener( cacheName, mockListener2 );
-
-        // VERIFY
-        assertEquals( "Wrong listener id.", 1, mockListener1.getListenerId() );
-        assertEquals( "Wrong listener id.", 2, mockListener2.getListenerId() );
-        assertEquals( "Wrong ip.", expectedIp1, server.getExtraInfoForRequesterId( 1 ) );
-        assertEquals( "Wrong ip.", expectedIp2, server.getExtraInfoForRequesterId( 2 ) );
-    }
-
-    // TODO: This test only works if preconfigured remote caches exist. Need to fix.
-//    /**
-//     * Add a listener. Pass the id of 0, verify that the server sets a new listener id. Do another
-//     * and verify that the second gets an id of 2.
-//     * <p>
-//     * @throws Exception
-//     */
-//    public void testAddListener_ToAll()
-//        throws Exception
-//    {
-//        MockRemoteCacheListener<String, String> mockListener1 = new MockRemoteCacheListener<>();
-//        mockListener1.localAddress = expectedIp1;
-//        MockRemoteCacheListener<String, String> mockListener2 = new MockRemoteCacheListener<>();
-//        mockListener2.localAddress = expectedIp2;
-//
-//        // DO WORK
-//        // don't specify the cache name
-//        server.addCacheListener( mockListener1 );
-//        server.addCacheListener( mockListener2 );
-//
-//        // VERIFY
-//        assertEquals( "Wrong listener id.", 1, mockListener1.getListenerId() );
-//        assertEquals( "Wrong listener id.", 2, mockListener2.getListenerId() );
-//        assertEquals( "Wrong ip.", expectedIp1, server.getExtraInfoForRequesterId( 1 ) );
-//        assertEquals( "Wrong ip.", expectedIp2, server.getExtraInfoForRequesterId( 2 ) );
-//    }
-
-    /**
-     * Add a listener. Pass the id of 0, verify that the server sets a new listener id. Do another
-     * and verify that the second gets an id of 2. Call remove Listener and verify that it is
-     * removed.
-     * <p>
-     * @throws Exception
-     */
-    public void testAddListener_ToAllThenRemove()
-        throws Exception
-    {
-        MockRemoteCacheListener<String, String> mockListener1 = new MockRemoteCacheListener<>();
-        MockRemoteCacheListener<String, String> mockListener2 = new MockRemoteCacheListener<>();
-
-        String cacheName = "testAddListenerToAllThenRemove";
-
-        // DO WORK
-        server.addCacheListener( cacheName, mockListener1 );
-        server.addCacheListener( cacheName, mockListener2 );
-
-        // VERIFY
-        assertEquals( "Wrong number of listeners.", 2, server.getCacheListeners( cacheName ).eventQMap.size() );
-        assertEquals( "Wrong listener id.", 1, mockListener1.getListenerId() );
-        assertEquals( "Wrong listener id.", 2, mockListener2.getListenerId() );
-
-        // DO WORK
-        server.removeCacheListener( cacheName, mockListener1.getListenerId() );
-        assertEquals( "Wrong number of listeners.", 1, server.getCacheListeners( cacheName ).eventQMap.size() );
-    }
-
-    /**
-     * Add a listener. Pass the id of 0, verify that the server sets a new listener id. Do another
-     * and verify that the second gets an id of 2. Call remove Listener and verify that it is
-     * removed.
-     * <p>
-     * @throws Exception
-     */
-    public void testAddListener_ToAllThenRemove_clusterType()
-        throws Exception
-    {
-        MockRemoteCacheListener<String, String> mockListener1 = new MockRemoteCacheListener<>();
-        mockListener1.remoteType = RemoteType.CLUSTER;
-        MockRemoteCacheListener<String, String> mockListener2 = new MockRemoteCacheListener<>();
-        mockListener2.remoteType = RemoteType.CLUSTER;
-
-        String cacheName = "testAddListenerToAllThenRemove";
-
-        // DO WORK
-        server.addCacheListener( cacheName, mockListener1 );
-        server.addCacheListener( cacheName, mockListener2 );
-
-        // VERIFY
-        assertEquals( "Wrong number of listeners.", 0, server.getCacheListeners( cacheName ).eventQMap.size() );
-        assertEquals( "Wrong number of listeners.", 2, server.getClusterListeners( cacheName ).eventQMap.size() );
-        assertEquals( "Wrong listener id.", 1, mockListener1.getListenerId() );
-        assertEquals( "Wrong listener id.", 2, mockListener2.getListenerId() );
-
-        // DO WORK
-        server.removeCacheListener( cacheName, mockListener1.getListenerId() );
-        assertEquals( "Wrong number of listeners.", 1, server.getClusterListeners( cacheName ).eventQMap.size() );
-        assertNull( "Should be no entry in the ip map.", server.getExtraInfoForRequesterId( 1 ) );
-    }
-
-    /**
-     * Register a listener and then verify that it is called when we put using a different listener
-     * id.
-     * @throws Exception
-     */
-    public void testSimpleRegisterListenerAndPut()
-        throws Exception
-    {
-        IRemoteCacheServerAttributes rcsa = new RemoteCacheServerAttributes();
-        rcsa.setConfigFileName( "/TestRemoteCacheServer.ccf" );
-
-        Properties config = RemoteUtils.loadProps(rcsa.getConfigFileName());
-        MockRemoteCacheListener<String, Long> mockListener = new MockRemoteCacheListener<>();
-        RemoteCacheServer<String, Long> server = new RemoteCacheServer<>( rcsa, config );
-
-        String cacheName = "testSimpleRegisterListenerAndPut";
-        server.addCacheListener( cacheName, mockListener );
-
-        // DO WORK
-        List<ICacheElement<String, Long>> inputItems = new LinkedList<>();
-        int numToPut = 10;
-
-        for ( int i = 0; i < numToPut; i++ )
-        {
-            ICacheElement<String, Long> element = new CacheElement<>( cacheName, String.valueOf( i ), Long.valueOf( i ) );
-            inputItems.add( element );
-            server.update( element, 9999 );
-        }
-
-        Thread.sleep( 100 );
-        Thread.yield();
-        Thread.sleep( 100 );
-
-        // VERIFY
-        assertEquals( "Wrong number of items put to listener.", numToPut, mockListener.putItems.size() );
-        for ( int i = 0; i < numToPut; i++ )
-        {
-            assertEquals( "Wrong item.", inputItems.get( i ), mockListener.putItems.get( i ) );
-        }
-
-        server.shutdown();
-    }
-
-    /**
-     * Register a listener and then verify that it is called when we put using a different listener
-     * id. The updates should come from a cluster listener and local cluster consistency should be
-     * true.
-     * <p>
-     * @throws Exception
-     */
-    public void testSimpleRegisterListenerAndPut_FromClusterWithLCC()
-        throws Exception
-    {
-        // SETUP
-        IRemoteCacheServerAttributes rcsa = new RemoteCacheServerAttributes();
-        rcsa.setLocalClusterConsistency( true );
-        rcsa.setConfigFileName( "/TestRemoteCacheServer.ccf" );
-        Properties config = RemoteUtils.loadProps(rcsa.getConfigFileName());
-        RemoteCacheServer<String, Long> server = new RemoteCacheServer<>( rcsa, config );
-
-        // this is to get the listener id for inserts.
-        MockRemoteCacheListener<String, Long> clusterListener = new MockRemoteCacheListener<>();
-        clusterListener.remoteType = RemoteType.CLUSTER;
-
-        // this should get the updates
-        MockRemoteCacheListener<String, Long> localListener = new MockRemoteCacheListener<>();
-        localListener.remoteType = RemoteType.LOCAL;
-
-        String cacheName = "testSimpleRegisterListenerAndPut_FromClusterWithLCC";
-        server.addCacheListener( cacheName, clusterListener );
-        server.addCacheListener( cacheName, localListener );
-
-        // DO WORK
-        List<ICacheElement<String, Long>> inputItems = new LinkedList<>();
-        int numToPut = 10;
-
-        for ( int i = 0; i < numToPut; i++ )
-        {
-            ICacheElement<String, Long> element = new CacheElement<>( cacheName, String.valueOf( i ), Long.valueOf( i ) );
-            inputItems.add( element );
-            // update using the cluster listener id
-            server.update( element, clusterListener.getListenerId() );
-        }
-
-        SleepUtil.sleepAtLeast( 200 );
-        Thread.yield();
-        SleepUtil.sleepAtLeast( 200 );
-
-        // VERIFY
-        assertEquals( "Wrong number of items put to listener.", numToPut, localListener.putItems.size() );
-        for ( int i = 0; i < numToPut; i++ )
-        {
-            assertEquals( "Wrong item.", inputItems.get( i ), localListener.putItems.get( i ) );
-        }
-
-        server.shutdown();
-    }
-
-    /**
-     * Register a listener and then verify that it is called when we put using a different listener
-     * id.
-     * @throws Exception
-     */
-    public void testSimpleRegisterListenerAndRemove()
-        throws Exception
-    {
-        MockRemoteCacheListener<String, String> mockListener = new MockRemoteCacheListener<>();
-
-        String cacheName = "testSimpleRegisterListenerAndPut";
-        server.addCacheListener( cacheName, mockListener );
-
-        // DO WORK
-        int numToPut = 10;
-
-        for ( int i = 0; i < numToPut; i++ )
-        {
-            // use a junk listener id
-            server.remove( cacheName, String.valueOf( i ), 9999 );
-        }
-
-        Thread.sleep( 100 );
-        Thread.yield();
-        Thread.sleep( 100 );
-
-        // VERIFY
-        assertEquals( "Wrong number of items removed from listener.", numToPut, mockListener.removedKeys.size() );
-        for ( int i = 0; i < numToPut; i++ )
-        {
-            assertEquals( "Wrong key.", String.valueOf( i ), mockListener.removedKeys.get( i ) );
-        }
-    }
-
-    /**
-     * Verify event log calls.
-     * <p>
-     * @throws Exception
-     */
-    public void testUpdate_simple()
-        throws Exception
-    {
-        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
-        server.setCacheEventLogger( cacheEventLogger );
-
-        ICacheElement<String, String> item = new CacheElement<>( "region", "key", "value" );
-
-        // DO WORK
-        server.update( item );
-
-        // VERIFY
-        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
-        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
-    }
-
-    /**
-     * Verify event log calls.
-     * <p>
-     * @throws Exception
-     */
-    public void testGet_simple()
-        throws Exception
-    {
-        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
-        server.setCacheEventLogger( cacheEventLogger );
-
-        // DO WORK
-        server.get( "region", "key" );
-
-        // VERIFY
-        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
-        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
-    }
-
-    /**
-     * Verify event log calls.
-     * <p>
-     * @throws Exception
-     */
-    public void testGetMatching_simple()
-        throws Exception
-    {
-        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
-        server.setCacheEventLogger( cacheEventLogger );
-
-        // DO WORK
-        server.getMatching( "region", "pattern", 0 );
-
-        // VERIFY
-        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
-        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
-    }
-
-    /**
-     * Verify event log calls.
-     * <p>
-     * @throws Exception
-     */
-    public void testGetMultiple_simple()
-        throws Exception
-    {
-        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
-        server.setCacheEventLogger( cacheEventLogger );
-
-        // DO WORK
-        server.getMultiple( "region", new HashSet<>() );
-
-        // VERIFY
-        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
-        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
-    }
-
-    /**
-     * Verify event log calls.
-     * <p>
-     * @throws Exception
-     */
-    public void testRemove_simple()
-        throws Exception
-    {
-        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
-        server.setCacheEventLogger( cacheEventLogger );
-
-        // DO WORK
-        server.remove( "region", "key" );
-
-        // VERIFY
-        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
-        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
-    }
-
-    /**
-     * Verify event log calls.
-     * <p>
-     * @throws Exception
-     */
-    public void testRemoveAll_simple()
-        throws Exception
-    {
-        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
-        server.setCacheEventLogger( cacheEventLogger );
-
-        // DO WORK
-        server.removeAll( "region" );
-
-        // VERIFY
-        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
-        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/server/TimeoutConfigurableRMISocketFactoryUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/server/TimeoutConfigurableRMISocketFactoryUnitTest.java
deleted file mode 100644
index ed76c0a..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/server/TimeoutConfigurableRMISocketFactoryUnitTest.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.server;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-
-import java.io.IOException;
-import java.net.ServerSocket;
-import java.net.Socket;
-
-/** Unit tests for the custom factory */
-public class TimeoutConfigurableRMISocketFactoryUnitTest
-    extends TestCase
-{
-    /**
-     * Simple test to see that we can create a server socket and connect.
-     * <p>
-     * @throws IOException
-     */
-    public void testCreateAndConnect() throws IOException
-    {
-        // SETUP
-        int port = 3455;
-        String host = "localhost";
-        TimeoutConfigurableRMISocketFactory factory = new TimeoutConfigurableRMISocketFactory();
-
-        // DO WORK
-        ServerSocket serverSocket = factory.createServerSocket( port );
-        Socket socket = factory.createSocket( host, port );
-        socket.close();
-        serverSocket.close();
-
-        // VERIFY
-        // passive, no errors
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/util/RemoteCacheRequestFactoryUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/util/RemoteCacheRequestFactoryUnitTest.java
deleted file mode 100644
index e43688c..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/auxiliary/remote/util/RemoteCacheRequestFactoryUnitTest.java
+++ /dev/null
@@ -1,144 +0,0 @@
-package org.apache.commons.jcs.auxiliary.remote.util;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.auxiliary.remote.value.RemoteCacheRequest;
-import org.apache.commons.jcs.auxiliary.remote.value.RemoteRequestType;
-import org.apache.commons.jcs.engine.CacheElement;
-
-import java.io.Serializable;
-import java.util.Collections;
-import java.util.Set;
-
-/** Unit tests for the request creator. */
-public class RemoteCacheRequestFactoryUnitTest
-    extends TestCase
-{
-    /** Simple test */
-    public void testCreateGetRequest_Normal()
-    {
-        // SETUP
-        String cacheName = "test";
-        Serializable key = "key";
-        long requesterId = 2;
-
-        // DO WORK
-        RemoteCacheRequest<Serializable, Serializable> result =
-            RemoteCacheRequestFactory.createGetRequest( cacheName, key, requesterId );
-
-        // VERIFY
-        assertNotNull( "Should have a result", result );
-        assertEquals( "Wrong cacheName", cacheName, result.getCacheName() );
-        assertEquals( "Wrong type", RemoteRequestType.GET, result.getRequestType() );
-    }
-
-    /** Simple test */
-    public void testCreateGetMatchingRequest_Normal()
-    {
-        // SETUP
-        String cacheName = "test";
-        String pattern = "pattern";
-        long requesterId = 2;
-
-        // DO WORK
-        RemoteCacheRequest<Serializable, Serializable> result =
-            RemoteCacheRequestFactory.createGetMatchingRequest( cacheName, pattern, requesterId );
-
-        // VERIFY
-        assertNotNull( "Should have a result", result );
-        assertEquals( "Wrong cacheName", cacheName, result.getCacheName() );
-        assertEquals( "Wrong type", RemoteRequestType.GET_MATCHING, result.getRequestType() );
-    }
-
-    /** Simple test */
-    public void testCreateGetMultipleRequest_Normal()
-    {
-        // SETUP
-        String cacheName = "test";
-        Set<Serializable> keys = Collections.emptySet();
-        long requesterId = 2;
-
-        // DO WORK
-        RemoteCacheRequest<Serializable, Serializable> result =
-            RemoteCacheRequestFactory.createGetMultipleRequest( cacheName, keys, requesterId );
-
-        // VERIFY
-        assertNotNull( "Should have a result", result );
-        assertEquals( "Wrong cacheName", cacheName, result.getCacheName() );
-        assertEquals( "Wrong type", RemoteRequestType.GET_MULTIPLE, result.getRequestType() );
-    }
-
-    /** Simple test */
-    public void testCreateRemoveRequest_Normal()
-    {
-        // SETUP
-        String cacheName = "test";
-        Serializable key = "key";
-        long requesterId = 2;
-
-        // DO WORK
-        RemoteCacheRequest<Serializable, Serializable> result = RemoteCacheRequestFactory
-            .createRemoveRequest( cacheName, key, requesterId );
-
-        // VERIFY
-        assertNotNull( "Should have a result", result );
-        assertEquals( "Wrong cacheName", cacheName, result.getCacheName() );
-        assertEquals( "Wrong type", RemoteRequestType.REMOVE, result.getRequestType() );
-    }
-
-    /** Simple test */
-    public void testCreateRemoveAllRequest_Normal()
-    {
-        // SETUP
-        String cacheName = "test";
-        long requesterId = 2;
-
-        // DO WORK
-        RemoteCacheRequest<Serializable, Serializable> result =
-            RemoteCacheRequestFactory.createRemoveAllRequest( cacheName, requesterId );
-
-        // VERIFY
-        assertNotNull( "Should have a result", result );
-        assertEquals( "Wrong cacheName", cacheName, result.getCacheName() );
-        assertEquals( "Wrong type", RemoteRequestType.REMOVE_ALL, result.getRequestType() );
-    }
-
-    /** Simple test */
-    public void testCreateUpdateRequest_Normal()
-    {
-        // SETUP
-        String cacheName = "test";
-        Serializable key = "key";
-        long requesterId = 2;
-
-        CacheElement<Serializable, Serializable> element =
-            new CacheElement<>( cacheName, key, null );
-
-        // DO WORK
-        RemoteCacheRequest<Serializable, Serializable> result =
-            RemoteCacheRequestFactory.createUpdateRequest( element, requesterId );
-
-        // VERIFY
-        assertNotNull( "Should have a result", result );
-        assertEquals( "Wrong cacheName", cacheName, result.getCacheName() );
-        assertEquals( "Wrong type", RemoteRequestType.UPDATE, result.getRequestType() );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/CacheEventQueueFactoryUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/CacheEventQueueFactoryUnitTest.java
deleted file mode 100644
index b5d3637..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/CacheEventQueueFactoryUnitTest.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package org.apache.commons.jcs.engine;
-
-import org.apache.commons.jcs.auxiliary.remote.MockRemoteCacheListener;
-import org.apache.commons.jcs.engine.behavior.ICacheEventQueue;
-import org.apache.commons.jcs.engine.behavior.ICacheEventQueue.QueueType;
-import org.apache.commons.jcs.engine.behavior.ICacheListener;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-
-/** Unit tests for the CacheEventQueueFactory */
-public class CacheEventQueueFactoryUnitTest
-    extends TestCase
-{
-    /** Test create */
-    public void testCreateCacheEventQueue_Single()
-    {
-        // SETUP
-        QueueType eventQueueType = QueueType.SINGLE;
-        ICacheListener<String, String> listener = new MockRemoteCacheListener<>();
-        long listenerId = 1;
-
-        CacheEventQueueFactory<String, String> factory = new CacheEventQueueFactory<>();
-
-        // DO WORK
-        ICacheEventQueue<String, String> result = factory.createCacheEventQueue( listener, listenerId, "cacheName", "threadPoolName", eventQueueType );
-
-        // VERIFY
-        assertNotNull( "Should have a result", result );
-        assertTrue( "Wrong type", result.getQueueType() == QueueType.SINGLE );
-    }
-
-    /** Test create */
-    public void testCreateCacheEventQueue_Pooled()
-    {
-        // SETUP
-        QueueType eventQueueType = QueueType.POOLED;
-        ICacheListener<String, String> listener = new MockRemoteCacheListener<>();
-        long listenerId = 1;
-
-        CacheEventQueueFactory<String, String> factory = new CacheEventQueueFactory<>();
-
-        // DO WORK
-        ICacheEventQueue<String, String> result = factory.createCacheEventQueue( listener, listenerId, "cacheName", "threadPoolName", eventQueueType );
-
-        // VERIFY
-        assertNotNull( "Should have a result", result );
-        assertTrue( "Wrong type", result.getQueueType() == QueueType.POOLED );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/ElementAttributesUtils.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/ElementAttributesUtils.java
deleted file mode 100644
index 5da0af0..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/ElementAttributesUtils.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.engine;
-
-/**
- * Allow test access to set last access time without exposing public method
- */
-public class ElementAttributesUtils {
-    public static void setLastAccessTime(ElementAttributes ea, long time) {
-        ea.setLastAccessTime(time);
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/EventQueueConcurrentLoadTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/EventQueueConcurrentLoadTest.java
deleted file mode 100644
index a38267e..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/EventQueueConcurrentLoadTest.java
+++ /dev/null
@@ -1,358 +0,0 @@
-package org.apache.commons.jcs.engine;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheListener;
-
-import junit.extensions.ActiveTestSuite;
-import junit.framework.Test;
-import junit.framework.TestCase;
-
-/**
- * This test case is designed to makes sure there are no deadlocks in the event queue. The time to
- * live should be set to a very short interval to make a deadlock more likely.
- * <p>
- * @author Aaron Smuts
- */
-public class EventQueueConcurrentLoadTest
-    extends TestCase
-{
-    /** The queue implementation */
-    private static CacheEventQueue<String, String> queue = null;
-
-    /** The mock listener */
-    private static CacheListenerImpl<String, String> listen = null;
-
-    /** max failure setting */
-    private final int maxFailure = 3;
-
-    /** time to wait before retrying on failure. */
-    private final int waitBeforeRetry = 100;
-
-    /** very small idle time */
-    private final int idleTime = 2;
-
-    /**
-     * Constructor for the TestDiskCache object.
-     * @param testName
-     */
-    public EventQueueConcurrentLoadTest( String testName )
-    {
-        super( testName );
-    }
-
-    /**
-     * Main method passes this test to the text test runner.
-     * @param args
-     */
-    public static void main( String args[] )
-    {
-        String[] testCaseName = { EventQueueConcurrentLoadTest.class.getName() };
-        junit.textui.TestRunner.main( testCaseName );
-    }
-
-    /**
-     * A unit test suite for JUnit
-     * @return The test suite
-     */
-    public static Test suite()
-    {
-        ActiveTestSuite suite = new ActiveTestSuite();
-
-        suite.addTest( new EventQueueConcurrentLoadTest( "testRunPutTest1" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runPutTest( 200, 200 );
-            }
-        } );
-
-        suite.addTest( new EventQueueConcurrentLoadTest( "testRunPutTest2" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runPutTest( 1200, 1400 );
-            }
-        } );
-
-        suite.addTest( new EventQueueConcurrentLoadTest( "testRunRemoveTest1" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runRemoveTest( 2200 );
-            }
-        } );
-
-        suite.addTest( new EventQueueConcurrentLoadTest( "testRunPutTest4" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runPutTest( 5200, 6600 );
-            }
-        } );
-
-        suite.addTest( new EventQueueConcurrentLoadTest( "testRunRemoveTest2" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runRemoveTest( 5200 );
-            }
-        } );
-
-        suite.addTest( new EventQueueConcurrentLoadTest( "testRunPutDelayTest" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runPutDelayTest( 100, 6700 );
-            }
-        } );
-
-        return suite;
-    }
-
-    /**
-     * Test setup. Create the static queue to be used by all tests
-     */
-    @Override
-    public void setUp()
-    {
-        listen = new CacheListenerImpl<>();
-        queue = new CacheEventQueue<>( listen, 1L, "testCache1", maxFailure, waitBeforeRetry );
-
-        queue.setWaitToDieMillis( idleTime );
-    }
-
-    /**
-     * Adds put events to the queue.
-     * @param end
-     * @param expectedPutCount
-     * @throws Exception
-     */
-    public void runPutTest( int end, int expectedPutCount )
-        throws Exception
-    {
-        for ( int i = 0; i <= end; i++ )
-        {
-            CacheElement<String, String> elem = new CacheElement<>( "testCache1", i + ":key", i + "data" );
-            queue.addPutEvent( elem );
-        }
-
-        while ( !queue.isEmpty() )
-        {
-            synchronized ( this )
-            {
-                System.out.println( "queue is still busy, waiting 250 millis" );
-                this.wait( 250 );
-            }
-        }
-        System.out.println( "queue is empty, comparing putCount" );
-
-        // this becomes less accurate with each test. It should never fail. If
-        // it does things are very off.
-        assertTrue( "The put count [" + listen.putCount + "] is below the expected minimum threshold ["
-            + expectedPutCount + "]", listen.putCount >= ( expectedPutCount - 1 ) );
-
-    }
-
-    /**
-     * Add remove events to the event queue.
-     * @param end
-     * @throws Exception
-     */
-    public void runRemoveTest( int end )
-        throws Exception
-    {
-        for ( int i = 0; i <= end; i++ )
-        {
-            queue.addRemoveEvent( i + ":key" );
-        }
-
-    }
-
-    /**
-     * Test putting and a delay. Waits until queue is empty to start.
-     * @param end
-     * @param expectedPutCount
-     * @throws Exception
-     */
-    public void runPutDelayTest( int end, int expectedPutCount )
-        throws Exception
-    {
-        while ( !queue.isEmpty() )
-        {
-            synchronized ( this )
-            {
-                System.out.println( "queue is busy, waiting 250 millis to begin" );
-                this.wait( 250 );
-            }
-        }
-        System.out.println( "queue is empty, begin" );
-
-        // get it going
-        CacheElement<String, String> elem = new CacheElement<>( "testCache1", "a:key", "adata" );
-        queue.addPutEvent( elem );
-
-        for ( int i = 0; i <= end; i++ )
-        {
-            synchronized ( this )
-            {
-                if ( i % 2 == 0 )
-                {
-                    this.wait( idleTime );
-                }
-                else
-                {
-                    this.wait( idleTime / 2 );
-                }
-            }
-            CacheElement<String, String> elem2 = new CacheElement<>( "testCache1", i + ":key", i + "data" );
-            queue.addPutEvent( elem2 );
-        }
-
-        while ( !queue.isEmpty() )
-        {
-            synchronized ( this )
-            {
-                System.out.println( "queue is still busy, waiting 250 millis" );
-                this.wait( 250 );
-            }
-        }
-        System.out.println( "queue is empty, comparing putCount" );
-
-        Thread.sleep( 1000 );
-
-        // this becomes less accurate with each test. It should never fail. If
-        // it does things are very off.
-        assertTrue( "The put count [" + listen.putCount + "] is below the expected minimum threshold ["
-            + expectedPutCount + "]", listen.putCount >= ( expectedPutCount - 1 ) );
-
-    }
-
-    /**
-     * This is a dummy cache listener to use when testing the event queue.
-     */
-    protected static class CacheListenerImpl<K, V>
-        implements ICacheListener<K, V>
-    {
-        /**
-         * <code>putCount</code>
-         */
-        protected int putCount = 0;
-
-        /**
-         * <code>removeCount</code>
-         */
-        protected int removeCount = 0;
-
-        /**
-         * @param item
-         * @throws IOException
-         */
-        @Override
-        public void handlePut( ICacheElement<K, V> item )
-            throws IOException
-        {
-            synchronized ( this )
-            {
-                putCount++;
-            }
-        }
-
-        /**
-         * @param cacheName
-         * @param key
-         * @throws IOException
-         */
-        @Override
-        public void handleRemove( String cacheName, K key )
-            throws IOException
-        {
-            synchronized ( this )
-            {
-                removeCount++;
-            }
-
-        }
-
-        /**
-         * @param cacheName
-         * @throws IOException
-         */
-        @Override
-        public void handleRemoveAll( String cacheName )
-            throws IOException
-        {
-            // TODO Auto-generated method stub
-
-        }
-
-        /**
-         * @param cacheName
-         * @throws IOException
-         */
-        @Override
-        public void handleDispose( String cacheName )
-            throws IOException
-        {
-            // TODO Auto-generated method stub
-
-        }
-
-        /**
-         * @param id
-         * @throws IOException
-         */
-        @Override
-        public void setListenerId( long id )
-            throws IOException
-        {
-            // TODO Auto-generated method stub
-
-        }
-
-        /**
-         * @return 0
-         * @throws IOException
-         */
-        @Override
-        public long getListenerId()
-            throws IOException
-        {
-            // TODO Auto-generated method stub
-            return 0;
-        }
-
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/MockCacheServiceNonLocal.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/MockCacheServiceNonLocal.java
deleted file mode 100644
index 662d2d7..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/MockCacheServiceNonLocal.java
+++ /dev/null
@@ -1,245 +0,0 @@
-package org.apache.commons.jcs.engine;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheServiceNonLocal;
-
-/**
- * This is a mock impl of the non local cache service.
- */
-public class MockCacheServiceNonLocal<K, V>
-    implements ICacheServiceNonLocal<K, V>
-{
-    /** The key last passed to get */
-    public K lastGetKey;
-
-    /** The pattern last passed to get */
-    public String lastGetMatchingPattern;
-
-    /** The keya last passed to getMatching */
-    public Set<K> lastGetMultipleKeys;
-
-    /** The object that was last passed to update. */
-    public ICacheElement<K, V> lastUpdate;
-
-    /** List of updates. */
-    public List<ICacheElement<K, V>> updateRequestList = new ArrayList<>();
-
-    /** List of request ids. */
-    public List<Long> updateRequestIdList = new ArrayList<>();
-
-    /** The key that was last passed to remove. */
-    public K lastRemoveKey;
-
-    /** The cache name that was last passed to removeAll. */
-    public String lastRemoveAllCacheName;
-
-    /**
-     * @param cacheName
-     * @param key
-     * @param requesterId - identity of requester
-     * @return null
-     */
-    @Override
-    public ICacheElement<K, V> get( String cacheName, K key, long requesterId )
-    {
-        lastGetKey = key;
-        return null;
-    }
-
-    /**
-     * @param cacheName
-     * @return empty set
-     */
-    @Override
-    public Set<K> getKeySet( String cacheName )
-    {
-        return new HashSet<>();
-    }
-
-    /**
-     * Set the last remove key.
-     * <p>
-     * @param cacheName
-     * @param key
-     * @param requesterId - identity of requester
-     */
-    @Override
-    public void remove( String cacheName, K key, long requesterId )
-    {
-        lastRemoveKey = key;
-    }
-
-    /**
-     * Set the lastRemoveAllCacheName to the cacheName.
-     * <p>
-     * @param cacheName - region name
-     * @param requesterId - identity of requester
-     * @throws IOException
-     */
-    @Override
-    public void removeAll( String cacheName, long requesterId )
-        throws IOException
-    {
-        lastRemoveAllCacheName = cacheName;
-    }
-
-    /**
-     * Set the last update item.
-     * <p>
-     * @param item
-     * @param requesterId - identity of requester
-     */
-    @Override
-    public void update( ICacheElement<K, V> item, long requesterId )
-    {
-        lastUpdate = item;
-        updateRequestList.add( item );
-        updateRequestIdList.add( Long.valueOf( requesterId ) );
-    }
-
-    /**
-     * Do nothing.
-     * <p>
-     * @param cacheName
-     */
-    @Override
-    public void dispose( String cacheName )
-    {
-        return;
-    }
-
-    /**
-     * @param cacheName
-     * @param key
-     * @return null
-     */
-    @Override
-    public ICacheElement<K, V> get( String cacheName, K key )
-    {
-        return get( cacheName, key, 0 );
-    }
-
-    /**
-     * Do nothing.
-     */
-    @Override
-    public void release()
-    {
-        return;
-    }
-
-    /**
-     * Set the last remove key.
-     * <p>
-     * @param cacheName
-     * @param key
-     */
-    @Override
-    public void remove( String cacheName, K key )
-    {
-        lastRemoveKey = key;
-    }
-
-    /**
-     * Set the last remove all cache name.
-     * <p>
-     * @param cacheName
-     */
-    @Override
-    public void removeAll( String cacheName )
-    {
-        lastRemoveAllCacheName = cacheName;
-    }
-
-    /**
-     * Set the last update item.
-     * <p>
-     * @param item
-     */
-    @Override
-    public void update( ICacheElement<K, V> item )
-    {
-        lastUpdate = item;
-    }
-
-    /**
-     * @param cacheName
-     * @param keys
-     * @param requesterId - identity of requester
-     * @return empty map
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys, long requesterId )
-    {
-        lastGetMultipleKeys = keys;
-        return new HashMap<>();
-    }
-
-    /**
-     * @param cacheName
-     * @param keys
-     * @return empty map
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys )
-    {
-        return getMultiple( cacheName, keys, 0 );
-    }
-
-    /**
-     * Returns an empty map. Zombies have no internal data.
-     * <p>
-     * @param cacheName
-     * @param pattern
-     * @return an empty map
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern )
-        throws IOException
-    {
-        return getMatching( cacheName, pattern, 0 );
-    }
-
-    /**
-     * @param cacheName
-     * @param pattern
-     * @param requesterId
-     * @return Map
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern, long requesterId )
-        throws IOException
-    {
-        lastGetMatchingPattern = pattern;
-        return new HashMap<>();
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/SystemPropertyUsageUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/SystemPropertyUsageUnitTest.java
deleted file mode 100644
index 3753ea6..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/SystemPropertyUsageUnitTest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-package org.apache.commons.jcs.engine;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.engine.control.CompositeCacheManager;
-import org.apache.commons.jcs.utils.props.PropertyLoader;
-
-import java.util.Properties;
-
-/**
- * Verify that system properties can override.
- */
-public class SystemPropertyUsageUnitTest
-    extends TestCase
-{
-    private static final String JCS_DEFAULT_CACHEATTRIBUTES_MAX_OBJECTS = "jcs.default.cacheattributes.MaxObjects";
-    private static final int testValue = 6789;
-
-    private CompositeCacheManager manager = null;
-    
-    @Override
-    protected void setUp() throws Exception
-    {
-       super.setUp();
-       //First shut down any previously running manager.
-       manager = CompositeCacheManager.getInstance();
-       manager.shutDown();
-    }
-
-	/**
-	 * @see junit.framework.TestCase#tearDown()
-	 */
-	@Override
-	protected void tearDown() throws Exception
-	{
-		if (manager != null)
-		{
-			manager.shutDown();
-		}
-
-        System.clearProperty(JCS_DEFAULT_CACHEATTRIBUTES_MAX_OBJECTS);
-		super.tearDown();
-	}
-
-	/**
-     * Verify that the system properties are used.
-     * @throws Exception
-     *
-     */
-    public void testSystemPropertyUsage()
-        throws Exception
-    {
-        System.setProperty( JCS_DEFAULT_CACHEATTRIBUTES_MAX_OBJECTS, String.valueOf(testValue) );
-        
-        JCS.setConfigFilename( "/TestSystemPropertyUsage.ccf" );
-        
-        CacheAccess<String, String> jcs = JCS.getInstance( "someCacheNotInFile" );
-
-        manager = CompositeCacheManager.getInstance();
-
-        assertEquals( "System property value is not reflected.", testValue, jcs.getCacheAttributes().getMaxObjects());
-    }
-
-    /**
-     * Verify that the system properties are not used is specified.
-     *
-     * @throws Exception
-     *
-     */
-    public void testSystemPropertyUsage_inactive()
-        throws Exception
-    {
-        System.setProperty( JCS_DEFAULT_CACHEATTRIBUTES_MAX_OBJECTS, String.valueOf(testValue) );
-
-        manager = CompositeCacheManager.getUnconfiguredInstance();
-
-        Properties props = PropertyLoader.loadProperties( "TestSystemPropertyUsage.ccf" );
-
-        manager.configure( props, false );
-
-        CacheAccess<String, String> jcs = JCS.getInstance( "someCacheNotInFile" );
-
-        assertEquals( "System property value should not be reflected",
-                      Integer.parseInt( props.getProperty( JCS_DEFAULT_CACHEATTRIBUTES_MAX_OBJECTS ) ),
-                      jcs.getCacheAttributes().getMaxObjects());
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/ZombieCacheServiceNonLocalUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/ZombieCacheServiceNonLocalUnitTest.java
deleted file mode 100644
index 1fa5bb6..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/ZombieCacheServiceNonLocalUnitTest.java
+++ /dev/null
@@ -1,125 +0,0 @@
-package org.apache.commons.jcs.engine;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-
-/**
- * Tests for the zombie remote cache service.
- */
-public class ZombieCacheServiceNonLocalUnitTest
-    extends TestCase
-{
-    /**
-     * Verify that an update event gets added and then is sent to the service passed to propagate.
-     * <p>
-     * @throws Exception
-     */
-    public void testUpdateThenWalk()
-        throws Exception
-    {
-        // SETUP
-        MockCacheServiceNonLocal<String, String> service = new MockCacheServiceNonLocal<>();
-
-        ZombieCacheServiceNonLocal<String, String> zombie = new ZombieCacheServiceNonLocal<>( 10 );
-
-        String cacheName = "testUpdate";
-
-        // DO WORK
-        ICacheElement<String, String> element = new CacheElement<>( cacheName, "key", "value" );
-        zombie.update( element, 123l );
-        zombie.propagateEvents( service );
-
-        // VERIFY
-        assertEquals( "Updated element is not as expected.", element, service.lastUpdate );
-    }
-
-    /**
-     * Verify that nothing is added if the max is set to 0.
-     * <p>
-     * @throws Exception
-     */
-    public void testUpdateThenWalk_zeroSize()
-        throws Exception
-    {
-        // SETUP
-        MockCacheServiceNonLocal<String, String> service = new MockCacheServiceNonLocal<>();
-
-        ZombieCacheServiceNonLocal<String, String> zombie = new ZombieCacheServiceNonLocal<>( 0 );
-
-        String cacheName = "testUpdate";
-
-        // DO WORK
-        ICacheElement<String, String> element = new CacheElement<>( cacheName, "key", "value" );
-        zombie.update( element, 123l );
-        zombie.propagateEvents( service );
-
-        // VERIFY
-        assertNull( "Nothing should have been put to the service.", service.lastUpdate );
-    }
-
-    /**
-     * Verify that a remove event gets added and then is sent to the service passed to propagate.
-     * <p>
-     * @throws Exception
-     */
-    public void testRemoveThenWalk()
-        throws Exception
-    {
-        // SETUP
-        MockCacheServiceNonLocal<String, String> service = new MockCacheServiceNonLocal<>();
-
-        ZombieCacheServiceNonLocal<String, String> zombie = new ZombieCacheServiceNonLocal<>( 10 );
-
-        String cacheName = "testRemoveThenWalk";
-        String key = "myKey";
-
-        // DO WORK
-        zombie.remove( cacheName, key, 123l );
-        zombie.propagateEvents( service );
-
-        // VERIFY
-        assertEquals( "Updated element is not as expected.", key, service.lastRemoveKey );
-    }
-
-    /**
-     * Verify that a removeAll event gets added and then is sent to the service passed to propagate.
-     * <p>
-     * @throws Exception
-     */
-    public void testRemoveAllThenWalk()
-        throws Exception
-    {
-        // SETUP
-        MockCacheServiceNonLocal<String, String> service = new MockCacheServiceNonLocal<>();
-
-        ZombieCacheServiceNonLocal<String, String> zombie = new ZombieCacheServiceNonLocal<>( 10 );
-
-        String cacheName = "testRemoveThenWalk";
-
-        // DO WORK
-        zombie.removeAll( cacheName, 123l );
-        zombie.propagateEvents( service );
-
-        // VERIFY
-        assertEquals( "Updated element is not as expected.", cacheName, service.lastRemoveAllCacheName );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/control/CacheManagerStatsUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/control/CacheManagerStatsUnitTest.java
deleted file mode 100644
index 9ae693e..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/control/CacheManagerStatsUnitTest.java
+++ /dev/null
@@ -1,71 +0,0 @@
-package org.apache.commons.jcs.engine.control;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.engine.stats.behavior.ICacheStats;
-
-/**
- * @author Aaron Smuts
- *
- */
-public class CacheManagerStatsUnitTest
-    extends TestCase
-{
-
-    /**
-     * Just get the stats after putting a couple entries in the cache.
-     *
-     * @throws Exception
-     */
-    public void testSimpleGetStats() throws Exception
-    {
-        CacheAccess<String, String> cache = JCS.getInstance( "testCache1" );
-
-        // 1 miss, 1 hit, 1 put
-        cache.get( "testKey" );
-        cache.put( "testKey", "testdata" );
-        // should have 4 hits
-        cache.get( "testKey" );
-        cache.get( "testKey" );
-        cache.get( "testKey" );
-        cache.get( "testKey" );
-
-        CompositeCacheManager mgr = CompositeCacheManager.getInstance();
-        String statsString = mgr.getStats();
-
-//        System.out.println( statsString );
-
-        assertTrue( "Should have the cacheName in here.", statsString.indexOf("testCache1") != -1 );
-        assertTrue( "Should have the HitCountRam in here.", statsString.indexOf("HitCountRam") != -1 );
-        assertTrue( "Should have the 4 in here.", statsString.indexOf("4") != -1 );
-
-        ICacheStats[] stats = mgr.getStatistics();
-        int statsLen = stats.length;
-//        System.out.println( "statsLen = " + statsLen );
-        for ( int i = 0; i < statsLen; i++ )
-        {
-            // TODO finish
-        }
-    }
-
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/control/CompositeCacheConfiguratorUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/control/CompositeCacheConfiguratorUnitTest.java
deleted file mode 100644
index c05d574..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/control/CompositeCacheConfiguratorUnitTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-package org.apache.commons.jcs.engine.control;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.Properties;
-
-import junit.framework.TestCase;
-
-import org.apache.commons.jcs.auxiliary.AuxiliaryCache;
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheConfigurator;
-import org.apache.commons.jcs.auxiliary.MockAuxiliaryCache;
-import org.apache.commons.jcs.auxiliary.MockAuxiliaryCacheAttributes;
-import org.apache.commons.jcs.auxiliary.MockAuxiliaryCacheFactory;
-import org.apache.commons.jcs.engine.logging.MockCacheEventLogger;
-
-/** Unit tests for the configurator. */
-public class CompositeCacheConfiguratorUnitTest
-    extends TestCase
-{
-    /**
-     * Verify that we can parse the event logger correctly
-     */
-    public void testParseAuxiliary_CacheEventLogger_Normal()
-    {
-        // SETUP
-        String regionName = "MyRegion";
-
-        String auxName = "MockAux";
-        String auxPrefix = CompositeCacheConfigurator.AUXILIARY_PREFIX + auxName;
-        String auxiliaryClassName = MockAuxiliaryCacheFactory.class.getName();
-        String eventLoggerClassName = MockCacheEventLogger.class.getName();
-        String auxiliaryAttributeClassName = MockAuxiliaryCacheAttributes.class.getName();
-
-        Properties props = new Properties();
-        props.put( auxPrefix, auxiliaryClassName );
-        props.put( auxPrefix + CompositeCacheConfigurator.ATTRIBUTE_PREFIX, auxiliaryAttributeClassName );
-        props.put( auxPrefix + AuxiliaryCacheConfigurator.CACHE_EVENT_LOGGER_PREFIX, eventLoggerClassName );
-
-//        System.out.print( props );
-
-        CompositeCacheManager manager = CompositeCacheManager.getUnconfiguredInstance();
-        CompositeCacheConfigurator configurator = new CompositeCacheConfigurator();
-
-        // DO WORK
-        AuxiliaryCache<String, String> aux = configurator.parseAuxiliary( props, manager, auxName, regionName );
-        MockAuxiliaryCache<String, String> result = (MockAuxiliaryCache<String, String>)aux;
-
-        // VERIFY
-        assertNotNull( "Should have an auxcache.", result );
-        assertNotNull( "Should have an event logger.", result.getCacheEventLogger() );
-    }
-
-    /**
-     * Verify that we can parse the spool chunk size
-     */
-    public void testParseSpoolChunkSize_Normal()
-    {
-        // SETUP
-        String regionName = "MyRegion";
-        int chunkSize = 5;
-
-        Properties props = new Properties();
-        props.put( "jcs.default", "" );
-        props.put( "jcs.default.cacheattributes.SpoolChunkSize", String.valueOf( chunkSize ) );
-
-        CompositeCacheManager manager = CompositeCacheManager.getUnconfiguredInstance();
-
-        // DO WORK
-        manager.configure( props );
-
-        // VERIFY
-        CompositeCache<String, String> cache = manager.getCache( regionName );
-        assertEquals( "Wrong chunkSize", cache.getCacheAttributes().getSpoolChunkSize(), chunkSize );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/control/CompositeCacheDiskUsageUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/control/CompositeCacheDiskUsageUnitTest.java
deleted file mode 100644
index c965166..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/control/CompositeCacheDiskUsageUnitTest.java
+++ /dev/null
@@ -1,510 +0,0 @@
-package org.apache.commons.jcs.engine.control;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-import junit.framework.TestCase;
-
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.access.exception.CacheException;
-import org.apache.commons.jcs.auxiliary.AbstractAuxiliaryCache;
-import org.apache.commons.jcs.auxiliary.AuxiliaryCache;
-import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes;
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.CacheStatus;
-import org.apache.commons.jcs.engine.CompositeCacheAttributes;
-import org.apache.commons.jcs.engine.ElementAttributes;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheType.CacheType;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheAttributes;
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-import org.apache.commons.jcs.engine.stats.behavior.IStats;
-
-/**
- * Tests of the disk usage settings for the CompositeCache.
- * <p>
- * @author Aaron Smuts
- */
-public class CompositeCacheDiskUsageUnitTest
-    extends TestCase
-{
-    private static final String CACHE_NAME = "testSpoolAllowed";
-
-    /**
-     * Test setup
-     */
-    @Override
-    public void setUp()
-    {
-        JCS.setConfigFilename( "/TestDiskCacheUsagePattern.ccf" );
-    }
-
-    /**
-     * Verify that the swap region is set to the correct pattern.
-     * <p>
-     * @throws CacheException
-     */
-    public void testSwapConfig()
-        throws CacheException
-    {
-        CacheAccess<String, String> swap = JCS.getInstance( "Swap" );
-        assertEquals( ICompositeCacheAttributes.DiskUsagePattern.SWAP, swap.getCacheAttributes()
-            .getDiskUsagePattern() );
-    }
-
-    /**
-     * Verify that the swap region is set to the correct pattern.
-     * <p>
-     * @throws CacheException
-     */
-    public void testUpdateConfig()
-        throws CacheException
-    {
-        CacheAccess<String, String> swap = JCS.getInstance( "Update" );
-        assertEquals( ICompositeCacheAttributes.DiskUsagePattern.UPDATE, swap.getCacheAttributes()
-            .getDiskUsagePattern() );
-    }
-
-    /**
-     * Setup a disk cache. Configure the disk usage pattern to swap. Call spool. Verify that the
-     * item is put to disk.
-     */
-    public void testSpoolAllowed()
-    {
-        // SETUP
-        ICompositeCacheAttributes cattr = new CompositeCacheAttributes();
-        cattr.setCacheName(CACHE_NAME);
-        cattr.setDiskUsagePattern( ICompositeCacheAttributes.DiskUsagePattern.SWAP );
-
-        IElementAttributes attr = new ElementAttributes();
-
-        CompositeCache<String, String> cache = new CompositeCache<>( cattr, attr );
-
-        MockAuxCache<String, String> mock = new MockAuxCache<>();
-        mock.cacheType = CacheType.DISK_CACHE;
-
-        @SuppressWarnings("unchecked")
-        AuxiliaryCache<String, String>[] auxArray = new AuxiliaryCache[] { mock };
-        cache.setAuxCaches( auxArray );
-
-        ICacheElement<String, String> inputElement = new CacheElement<>( CACHE_NAME, "key", "value" );
-
-        // DO WORK
-        cache.spoolToDisk( inputElement );
-
-        // VERIFY
-        assertEquals( "Wrong number of calls to the disk cache update.", 1, mock.updateCount );
-        assertEquals( "Wrong element updated.", inputElement, mock.lastUpdatedItem );
-    }
-
-    /**
-     * Setup a disk cache. Configure the disk usage pattern to not swap. Call spool. Verify that the
-     * item is not put to disk.
-     */
-    public void testSpoolNotAllowed()
-    {
-        // SETUP
-        ICompositeCacheAttributes cattr = new CompositeCacheAttributes();
-        cattr.setCacheName(CACHE_NAME);
-        cattr.setDiskUsagePattern( ICompositeCacheAttributes.DiskUsagePattern.UPDATE );
-
-        IElementAttributes attr = new ElementAttributes();
-
-        CompositeCache<String, String> cache = new CompositeCache<>( cattr, attr );
-
-        MockAuxCache<String, String> mock = new MockAuxCache<>();
-        mock.cacheType = CacheType.DISK_CACHE;
-
-        @SuppressWarnings("unchecked")
-        AuxiliaryCache<String, String>[] auxArray = new AuxiliaryCache[] { mock };
-        cache.setAuxCaches( auxArray );
-
-        ICacheElement<String, String> inputElement = new CacheElement<>( CACHE_NAME, "key", "value" );
-
-        // DO WORK
-        cache.spoolToDisk( inputElement );
-
-        // VERIFY
-        assertEquals( "Wrong number of calls to the disk cache update.", 0, mock.updateCount );
-    }
-
-    /**
-     * Setup a disk cache. Configure the disk usage pattern to UPDATE. Call updateAuxiliaries.
-     * Verify that the item is put to disk.
-     * <p>
-     * This tests that the items are put to disk on a normal put when the usage pattern is set
-     * appropriately.
-     * @throws IOException
-     */
-    public void testUpdateAllowed()
-        throws IOException
-    {
-        // SETUP
-        ICompositeCacheAttributes cattr = new CompositeCacheAttributes();
-        cattr.setCacheName(CACHE_NAME);
-        cattr.setDiskUsagePattern( ICompositeCacheAttributes.DiskUsagePattern.UPDATE );
-
-        IElementAttributes attr = new ElementAttributes();
-
-        CompositeCache<String, String> cache = new CompositeCache<>( cattr, attr );
-
-        MockAuxCache<String, String> mock = new MockAuxCache<>();
-        mock.cacheType = CacheType.DISK_CACHE;
-
-        @SuppressWarnings("unchecked")
-        AuxiliaryCache<String, String>[] auxArray = new AuxiliaryCache[] { mock };
-        cache.setAuxCaches( auxArray );
-
-        ICacheElement<String, String> inputElement = new CacheElement<>( CACHE_NAME, "key", "value" );
-
-        // DO WORK
-        cache.updateAuxiliaries( inputElement, true );
-
-        // VERIFY
-        assertEquals( "Wrong number of calls to the disk cache update.", 1, mock.updateCount );
-        assertEquals( "Wrong element updated.", inputElement, mock.lastUpdatedItem );
-    }
-
-    /**
-     * Setup a disk cache. Configure the disk usage pattern to UPDATE. Call updateAuxiliaries with
-     * local only set to false. Verify that the item is put to disk.
-     * <p>
-     * This tests that the items are put to disk on a normal put when the usage pattern is set
-     * appropriately. The local setting should have no impact on whether the item goes to disk.
-     * <p>
-     * @throws IOException
-     */
-    public void testUpdateAllowed_localFalse()
-        throws IOException
-    {
-        // SETUP
-        ICompositeCacheAttributes cattr = new CompositeCacheAttributes();
-        cattr.setCacheName(CACHE_NAME);
-        cattr.setDiskUsagePattern( ICompositeCacheAttributes.DiskUsagePattern.UPDATE );
-
-        IElementAttributes attr = new ElementAttributes();
-
-        CompositeCache<String, String> cache = new CompositeCache<>( cattr, attr );
-
-        MockAuxCache<String, String> mock = new MockAuxCache<>();
-        mock.cacheType = CacheType.DISK_CACHE;
-
-        @SuppressWarnings("unchecked")
-        AuxiliaryCache<String, String>[] auxArray = new AuxiliaryCache[] { mock };
-        cache.setAuxCaches( auxArray );
-
-        ICacheElement<String, String> inputElement = new CacheElement<>( CACHE_NAME, "key", "value" );
-
-        // DO WORK
-        cache.updateAuxiliaries( inputElement, false );
-
-        // VERIFY
-        assertEquals( "Wrong number of calls to the disk cache update.", 1, mock.updateCount );
-        assertEquals( "Wrong element updated.", inputElement, mock.lastUpdatedItem );
-    }
-
-    /**
-     * Setup a disk cache. Configure the disk usage pattern to SWAP. Call updateAuxiliaries. Verify
-     * that the item is not put to disk.
-     * <p>
-     * This tests that the items are not put to disk on a normal put when the usage pattern is set
-     * to SWAP.
-     * <p>
-     * @throws IOException
-     */
-    public void testUpdateNotAllowed()
-        throws IOException
-    {
-        // SETUP
-        ICompositeCacheAttributes cattr = new CompositeCacheAttributes();
-        cattr.setCacheName(CACHE_NAME);
-        cattr.setDiskUsagePattern( ICompositeCacheAttributes.DiskUsagePattern.SWAP );
-
-        IElementAttributes attr = new ElementAttributes();
-
-        CompositeCache<String, String> cache = new CompositeCache<>( cattr, attr );
-
-        MockAuxCache<String, String> mock = new MockAuxCache<>();
-        mock.cacheType = CacheType.DISK_CACHE;
-
-        @SuppressWarnings("unchecked")
-        AuxiliaryCache<String, String>[] auxArray = new AuxiliaryCache[] { mock };
-        cache.setAuxCaches( auxArray );
-
-        ICacheElement<String, String> inputElement = new CacheElement<>( CACHE_NAME, "key", "value" );
-
-        // DO WORK
-        cache.updateAuxiliaries( inputElement, true );
-
-        // VERIFY
-        assertEquals( "Wrong number of calls to the disk cache update.", 0, mock.updateCount );
-    }
-
-    /**
-     * Setup a disk cache. Configure the disk usage pattern to UPDATE. Call updateAuxiliaries.
-     * Verify that the item is put to disk.
-     * <p>
-     * This tests that the items are put to disk on a normal put when the usage pattern is set
-     * appropriately.
-     * @throws IOException
-     */
-    public void testUpdateAllowed_withOtherCaches()
-        throws IOException
-    {
-        // SETUP
-        ICompositeCacheAttributes cattr = new CompositeCacheAttributes();
-        cattr.setCacheName(CACHE_NAME);
-        cattr.setDiskUsagePattern( ICompositeCacheAttributes.DiskUsagePattern.UPDATE );
-
-        IElementAttributes attr = new ElementAttributes();
-
-        CompositeCache<String, String> cache = new CompositeCache<>( cattr, attr );
-
-        MockAuxCache<String, String> mock = new MockAuxCache<>();
-        mock.cacheType = CacheType.DISK_CACHE;
-
-        MockAuxCache<String, String> mockLateral = new MockAuxCache<>();
-        mockLateral.cacheType = CacheType.LATERAL_CACHE;
-
-        @SuppressWarnings("unchecked")
-        AuxiliaryCache<String, String>[] auxArray = new AuxiliaryCache[] { mock, mockLateral };
-        cache.setAuxCaches( auxArray );
-
-        ICacheElement<String, String> inputElement = new CacheElement<>( CACHE_NAME, "key", "value" );
-
-        // DO WORK
-        cache.updateAuxiliaries( inputElement, false );
-
-        // VERIFY
-        assertEquals( "Wrong number of calls to the disk cache update.", 1, mock.updateCount );
-        assertEquals( "Wrong element updated.", inputElement, mock.lastUpdatedItem );
-
-        assertEquals( "Wrong number of calls to the lateral cache update.", 1, mockLateral.updateCount );
-        assertEquals( "Wrong element updated with lateral.", inputElement, mockLateral.lastUpdatedItem );
-    }
-
-    /**
-     * Used to test the disk cache functionality.
-     * <p>
-     * @author Aaron Smuts
-     */
-    public static class MockAuxCache<K, V>
-        extends AbstractAuxiliaryCache<K, V>
-    {
-        /** The last item passed to update. */
-        public ICacheElement<K, V> lastUpdatedItem;
-
-        /** The number of times update was called. */
-        public int updateCount = 0;
-
-        /** The type that should be returned from getCacheType. */
-        public CacheType cacheType = CacheType.DISK_CACHE;
-
-        /** Resets counters and catchers. */
-        public void reset()
-        {
-            updateCount = 0;
-            lastUpdatedItem = null;
-        }
-
-        /**
-         * @param ce
-         * @throws IOException
-         */
-        @Override
-        public void update( ICacheElement<K, V> ce )
-            throws IOException
-        {
-            lastUpdatedItem = ce;
-            updateCount++;
-        }
-
-        /**
-         * @param key
-         * @return ICacheElement
-         * @throws IOException
-         */
-        @Override
-        public ICacheElement<K, V> get( K key )
-            throws IOException
-        {
-            return null;
-        }
-
-        /**
-         * Gets multiple items from the cache based on the given set of keys.
-         * <p>
-         * @param keys
-         * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is
-         *         no data in cache for any of these keys
-         */
-        @Override
-        public Map<K, ICacheElement<K, V>> getMultiple(Set<K> keys)
-        {
-            return new HashMap<>();
-        }
-
-        /**
-         * @param key
-         * @return false
-         * @throws IOException
-         */
-        @Override
-        public boolean remove( K key )
-            throws IOException
-        {
-            return false;
-        }
-
-        /** @throws IOException */
-        @Override
-        public void removeAll()
-            throws IOException
-        {
-            // noop
-        }
-
-        /** @throws IOException */
-        @Override
-        public void dispose()
-            throws IOException
-        {
-            // noop
-        }
-
-        /** @return 0 */
-        @Override
-        public int getSize()
-        {
-            return 0;
-        }
-
-        /** @return 0 */
-        @Override
-        public CacheStatus getStatus()
-        {
-            return CacheStatus.ALIVE;
-        }
-
-        /** @return null */
-        @Override
-        public String getCacheName()
-        {
-            return null;
-        }
-
-        /**
-         * @return null
-         * @throws IOException
-         */
-        @Override
-        public Set<K> getKeySet( )
-            throws IOException
-        {
-            return null;
-        }
-
-        /** @return null */
-        @Override
-        public IStats getStatistics()
-        {
-            return null;
-        }
-
-        /** @return null */
-        @Override
-        public String getStats()
-        {
-            return null;
-        }
-
-        /**
-         * Returns the setup cache type. This allows you to use this mock as multiple cache types.
-         * <p>
-         * @see org.apache.commons.jcs.engine.behavior.ICacheType#getCacheType()
-         * @return cacheType
-         */
-        @Override
-        public CacheType getCacheType()
-        {
-            return cacheType;
-        }
-
-        /**
-         * @return Returns the AuxiliaryCacheAttributes.
-         */
-        @Override
-        public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
-        {
-            return null;
-        }
-
-        /**
-         * @param cacheEventLogger
-         */
-        @Override
-        public void setCacheEventLogger( ICacheEventLogger cacheEventLogger )
-        {
-            // TODO Auto-generated method stub
-
-        }
-
-        /**
-         * @param elementSerializer
-         */
-        @Override
-        public void setElementSerializer( IElementSerializer elementSerializer )
-        {
-            // TODO Auto-generated method stub
-
-        }
-
-        /** @return null */
-        @Override
-        public String getEventLoggingExtraInfo()
-        {
-            // TODO Auto-generated method stub
-            return null;
-        }
-
-        /**
-         * @param pattern
-         * @return Collections.EMPTY_MAP;
-         * @throws IOException
-         */
-        @Override
-        public Map<K, ICacheElement<K, V>> getMatching(String pattern)
-            throws IOException
-        {
-            return Collections.emptyMap();
-        }
-
-
-    }
-
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/control/CompositeCacheManagerTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/control/CompositeCacheManagerTest.java
deleted file mode 100644
index 207062c..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/control/CompositeCacheManagerTest.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package org.apache.commons.jcs.engine.control;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-
-import org.apache.commons.jcs.engine.CacheStatus;
-import org.apache.commons.jcs.engine.CompositeCacheAttributes;
-
-/** Unit tests for the composite cache manager */
-public class CompositeCacheManagerTest
-    extends TestCase
-{
-
-    /**
-     * Verify that calling release, when there are active clients, the caches are correctly disposed or not.
-     */
-    public void testRelease()
-    {
-        // See JCS-184
-        // create the manager
-        CompositeCacheManager manager = CompositeCacheManager.getInstance();
-        // add a simple cache
-        CompositeCacheAttributes cacheAttributes = new CompositeCacheAttributes();
-        CompositeCache<String, String> cache = new CompositeCache<>(cacheAttributes, /* attr */ null);
-        manager.addCache("simple_cache", cache);
-        // add a client to the cache
-        CompositeCacheManager.getUnconfiguredInstance();
-        // won't release as there are still clients. Only disposed when release() is called by
-        // the last client
-        manager.release();
-        assertEquals("The cache was disposed during release!", CacheStatus.ALIVE, cache.getStatus());
-        manager.release();
-        assertEquals("The cache was NOT disposed during release!", CacheStatus.DISPOSED, cache.getStatus());
-    }
-
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/control/CompositeCacheUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/control/CompositeCacheUnitTest.java
deleted file mode 100644
index a7b0d3b..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/control/CompositeCacheUnitTest.java
+++ /dev/null
@@ -1,245 +0,0 @@
-package org.apache.commons.jcs.engine.control;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.auxiliary.AuxiliaryCache;
-import org.apache.commons.jcs.auxiliary.MockAuxiliaryCache;
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.CompositeCacheAttributes;
-import org.apache.commons.jcs.engine.ElementAttributes;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheType.CacheType;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheAttributes;
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-import org.apache.commons.jcs.engine.memory.MockMemoryCache;
-
-import java.io.IOException;
-import java.util.Map;
-
-/**
- * Tests that directly engage the composite cache.
- * <p>
- * @author Aaron Smuts
- */
-public class CompositeCacheUnitTest
-    extends TestCase
-{
-    /**
-     * Verify that the freeMemoryElements method on the memory cache is called on shutdown if there
-     * is a disk cache.
-     * <p>
-     * @throws IOException
-     */
-    public void testShutdownMemoryFlush()
-        throws IOException
-    {
-        // SETUP
-        String cacheName = "testCacheName";
-        String mockMemoryCacheClassName = "org.apache.commons.jcs.engine.memory.MockMemoryCache";
-        ICompositeCacheAttributes cattr = new CompositeCacheAttributes();
-        cattr.setMemoryCacheName( mockMemoryCacheClassName );
-
-        IElementAttributes attr = new ElementAttributes();
-
-        CompositeCache<String, Integer> cache = new CompositeCache<>( cattr, attr );
-
-        MockAuxiliaryCache<String, Integer> diskMock = new MockAuxiliaryCache<>();
-        diskMock.cacheType = CacheType.DISK_CACHE;
-        @SuppressWarnings("unchecked")
-        AuxiliaryCache<String, Integer>[] aux = new AuxiliaryCache[] { diskMock };
-        cache.setAuxCaches( aux );
-
-        // DO WORK
-        int numToInsert = 10;
-        for ( int i = 0; i < numToInsert; i++ )
-        {
-            ICacheElement<String, Integer> element = new CacheElement<>( cacheName, String.valueOf( i ), Integer.valueOf( i ) );
-            cache.update( element, false );
-        }
-
-        cache.dispose();
-
-        // VERIFY
-        MockMemoryCache<String, Integer> memoryCache = (MockMemoryCache<String, Integer>) cache.getMemoryCache();
-        assertEquals( "Wrong number freed.", numToInsert, memoryCache.lastNumberOfFreedElements );
-    }
-
-    /**
-     * Verify that the freeMemoryElements method on the memory cache is NOT called on shutdown if
-     * there is NOT a disk cache.
-     * <p>
-     * @throws IOException
-     */
-    public void testShutdownMemoryFlush_noDisk()
-        throws IOException
-    {
-        // SETUP
-        String cacheName = "testCacheName";
-        String mockMemoryCacheClassName = "org.apache.commons.jcs.engine.memory.MockMemoryCache";
-        ICompositeCacheAttributes cattr = new CompositeCacheAttributes();
-        cattr.setMemoryCacheName( mockMemoryCacheClassName );
-
-        IElementAttributes attr = new ElementAttributes();
-
-        CompositeCache<String, Integer> cache = new CompositeCache<>( cattr, attr );
-
-        MockAuxiliaryCache<String, Integer> diskMock = new MockAuxiliaryCache<>();
-        diskMock.cacheType = CacheType.REMOTE_CACHE;
-        @SuppressWarnings("unchecked")
-        AuxiliaryCache<String, Integer>[] aux = new AuxiliaryCache[] { diskMock };
-        cache.setAuxCaches( aux );
-
-        // DO WORK
-        int numToInsert = 10;
-        for ( int i = 0; i < numToInsert; i++ )
-        {
-            ICacheElement<String, Integer> element = new CacheElement<>( cacheName, String.valueOf( i ), Integer.valueOf( i ) );
-            cache.update( element, false );
-        }
-
-        cache.dispose();
-
-        // VERIFY
-        MockMemoryCache<String, Integer> memoryCache = (MockMemoryCache<String, Integer>) cache.getMemoryCache();
-        assertEquals( "Wrong number freed.", 0, memoryCache.lastNumberOfFreedElements );
-    }
-
-    /**
-     * Verify we can get some matching elements..
-     * <p>
-     * @throws IOException
-     */
-    public void testGetMatching_Normal()
-        throws IOException
-    {
-        // SETUP
-        int maxMemorySize = 1000;
-        String keyprefix1 = "MyPrefix1";
-        String keyprefix2 = "MyPrefix2";
-        String cacheName = "testGetMatching_Normal";
-        String memoryCacheClassName = "org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache";
-        ICompositeCacheAttributes cattr = new CompositeCacheAttributes();
-        cattr.setMemoryCacheName( memoryCacheClassName );
-        cattr.setMaxObjects( maxMemorySize );
-
-        IElementAttributes attr = new ElementAttributes();
-
-        CompositeCache<String, Integer> cache = new CompositeCache<>( cattr, attr );
-
-        MockAuxiliaryCache<String, Integer> diskMock = new MockAuxiliaryCache<>();
-        diskMock.cacheType = CacheType.DISK_CACHE;
-        @SuppressWarnings("unchecked")
-        AuxiliaryCache<String, Integer>[] aux = new AuxiliaryCache[] { diskMock };
-        cache.setAuxCaches( aux );
-
-        // DO WORK
-        int numToInsertPrefix1 = 10;
-        // insert with prefix1
-        for ( int i = 0; i < numToInsertPrefix1; i++ )
-        {
-            ICacheElement<String, Integer> element = new CacheElement<>( cacheName, keyprefix1 + String.valueOf( i ), Integer.valueOf( i ) );
-            cache.update( element, false );
-        }
-
-        int numToInsertPrefix2 = 50;
-        // insert with prefix1
-        for ( int i = 0; i < numToInsertPrefix2; i++ )
-        {
-            ICacheElement<String, Integer> element = new CacheElement<>( cacheName, keyprefix2 + String.valueOf( i ), Integer.valueOf( i ) );
-            cache.update( element, false );
-        }
-
-        Map<?, ?> result1 = cache.getMatching( keyprefix1 + "\\S+" );
-        Map<?, ?> result2 = cache.getMatching( keyprefix2 + "\\S+" );
-
-        // VERIFY
-        assertEquals( "Wrong number returned 1:", numToInsertPrefix1, result1.size() );
-        assertEquals( "Wrong number returned 2:", numToInsertPrefix2, result2.size() );
-    }
-
-    /**
-     * Verify we try a disk aux on a getMatching call.
-     * <p>
-     * @throws IOException
-     */
-    public void testGetMatching_NotOnDisk()
-        throws IOException
-    {
-        // SETUP
-        int maxMemorySize = 0;
-        String cacheName = "testGetMatching_NotOnDisk";
-        String memoryCacheClassName = "org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache";
-        ICompositeCacheAttributes cattr = new CompositeCacheAttributes();
-        cattr.setCacheName(cacheName);
-        cattr.setMemoryCacheName( memoryCacheClassName );
-        cattr.setMaxObjects( maxMemorySize );
-
-        IElementAttributes attr = new ElementAttributes();
-
-        CompositeCache<String, Integer> cache = new CompositeCache<>( cattr, attr );
-
-        MockAuxiliaryCache<String, Integer> diskMock = new MockAuxiliaryCache<>();
-        diskMock.cacheType = CacheType.DISK_CACHE;
-        @SuppressWarnings("unchecked")
-        AuxiliaryCache<String, Integer>[] aux = new AuxiliaryCache[] { diskMock };
-        cache.setAuxCaches( aux );
-
-        // DO WORK
-        cache.getMatching( "junk" );
-
-        // VERIFY
-        assertEquals( "Wrong number of calls", 1, diskMock.getMatchingCallCount );
-    }
-
-    /**
-     * Verify we try a remote  aux on a getMatching call.
-     * <p>
-     * @throws IOException
-     */
-    public void testGetMatching_NotOnRemote()
-        throws IOException
-    {
-        // SETUP
-        int maxMemorySize = 0;
-        String cacheName = "testGetMatching_NotOnDisk";
-        String memoryCacheClassName = "org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache";
-        ICompositeCacheAttributes cattr = new CompositeCacheAttributes();
-        cattr.setCacheName(cacheName);
-        cattr.setMemoryCacheName( memoryCacheClassName );
-        cattr.setMaxObjects( maxMemorySize );
-
-        IElementAttributes attr = new ElementAttributes();
-
-        CompositeCache<String, Integer> cache = new CompositeCache<>( cattr, attr );
-
-        MockAuxiliaryCache<String, Integer> diskMock = new MockAuxiliaryCache<>();
-        diskMock.cacheType = CacheType.REMOTE_CACHE;
-        @SuppressWarnings("unchecked")
-        AuxiliaryCache<String, Integer>[] aux = new AuxiliaryCache[] { diskMock };
-        cache.setAuxCaches( aux );
-
-        // DO WORK
-        cache.getMatching( "junk" );
-
-        // VERIFY
-        assertEquals( "Wrong number of calls", 1, diskMock.getMatchingCallCount );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/control/MockCompositeCacheManager.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/control/MockCompositeCacheManager.java
deleted file mode 100644
index 1099919..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/control/MockCompositeCacheManager.java
+++ /dev/null
@@ -1,126 +0,0 @@
-package org.apache.commons.jcs.engine.control;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.auxiliary.AuxiliaryCache;
-import org.apache.commons.jcs.engine.CompositeCacheAttributes;
-import org.apache.commons.jcs.engine.ElementAttributes;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
-import org.apache.commons.jcs.engine.behavior.IShutdownObserver;
-
-import java.util.Properties;
-
-/** For testing. */
-public class MockCompositeCacheManager
-    implements ICompositeCacheManager
-{
-    /** The cache that was returned. */
-    private CompositeCache<?, ?> cache;
-
-    /** Properties with which this manager was configured. This is exposed for other managers. */
-    private Properties configurationProperties;
-
-    /**
-     * @param cacheName
-     * @return Returns a CompositeCache
-     */
-    @Override
-    @SuppressWarnings("unchecked")
-    public <K, V> CompositeCache<K, V> getCache( String cacheName )
-    {
-        if ( cache == null )
-        {
-//            System.out.println( "Creating mock cache" );
-            CompositeCache<K, V> newCache =
-                new CompositeCache<>( new CompositeCacheAttributes(), new ElementAttributes() );
-            this.setCache( newCache );
-        }
-
-        return (CompositeCache<K, V>)cache;
-    }
-
-    @Override
-    public <K, V> AuxiliaryCache<K, V> getAuxiliaryCache(String auxName, String cacheName)
-    {
-        return null;
-    }
-
-    /**
-     * @param cache The cache to set.
-     */
-    public void setCache( CompositeCache<?, ?> cache )
-    {
-        this.cache = cache;
-    }
-
-    /**
-     * @return Returns the cache.
-     */
-    public CompositeCache<?, ?> getCache()
-    {
-        return cache;
-    }
-
-    /**
-     * This is exposed so other manager can get access to the props.
-     * <p>
-     * @param props
-     */
-    public void setConfigurationProperties( Properties props )
-    {
-        this.configurationProperties = props;
-    }
-
-    /**
-     * This is exposed so other manager can get access to the props.
-     * <p>
-     * @return the configurationProperties
-     */
-    @Override
-    public Properties getConfigurationProperties()
-    {
-        return configurationProperties;
-    }
-
-    /** @return Mock */
-    @Override
-    public String getStats()
-    {
-        return "Mock";
-    }
-
-	/**
-	 * @see org.apache.commons.jcs.engine.behavior.IShutdownObservable#registerShutdownObserver(org.apache.commons.jcs.engine.behavior.IShutdownObserver)
-	 */
-	@Override
-	public void registerShutdownObserver(IShutdownObserver observer)
-	{
-		// Do nothing
-	}
-
-	/**
-	 * @see org.apache.commons.jcs.engine.behavior.IShutdownObservable#deregisterShutdownObserver(org.apache.commons.jcs.engine.behavior.IShutdownObserver)
-	 */
-	@Override
-	public void deregisterShutdownObserver(IShutdownObserver observer)
-	{
-		// Do nothing
-	}
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/control/MockElementSerializer.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/control/MockElementSerializer.java
deleted file mode 100644
index 49885db..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/control/MockElementSerializer.java
+++ /dev/null
@@ -1,87 +0,0 @@
-package org.apache.commons.jcs.engine.control;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.utils.serialization.StandardSerializer;
-
-import java.io.IOException;
-
-/** For mocking. */
-public class MockElementSerializer
-    implements IElementSerializer
-{
-    /** test property */
-    private String testProperty;
-
-    /** What's used in the background */
-    private final StandardSerializer serializer = new StandardSerializer();
-
-    /** times out was called */
-    public int deSerializeCount = 0;
-
-    /** times in was called */
-    public int serializeCount = 0;
-
-    /**
-     * @param bytes
-     * @return Object
-     * @throws IOException
-     * @throws ClassNotFoundException
-     *
-     */
-    @Override
-    public <T> T deSerialize( byte[] bytes, ClassLoader loader )
-        throws IOException, ClassNotFoundException
-    {
-        deSerializeCount++;
-        return serializer.deSerialize( bytes, loader );
-    }
-
-    /**
-     * @param obj
-     * @return byte[]
-     * @throws IOException
-     *
-     */
-    @Override
-    public <T> byte[] serialize( T obj )
-        throws IOException
-    {
-        serializeCount++;
-        return serializer.serialize( obj );
-    }
-
-    /**
-     * @param testProperty
-     */
-    public void setTestProperty( String testProperty )
-    {
-        this.testProperty = testProperty;
-    }
-
-    /**
-     * @return testProperty
-     */
-    public String getTestProperty()
-    {
-        return testProperty;
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/control/event/ElementEventHandlerMockImpl.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/control/event/ElementEventHandlerMockImpl.java
deleted file mode 100644
index 8b56d66..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/control/event/ElementEventHandlerMockImpl.java
+++ /dev/null
@@ -1,186 +0,0 @@
-package org.apache.commons.jcs.engine.control.event;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.control.event.behavior.ElementEventType;
-import org.apache.commons.jcs.engine.control.event.behavior.IElementEvent;
-import org.apache.commons.jcs.engine.control.event.behavior.IElementEventHandler;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * @author aaronsm
- */
-public class ElementEventHandlerMockImpl
-    implements IElementEventHandler
-{
-    /** Times called. */
-    private int callCount = 0;
-
-    /** The logger */
-    private static final Log log = LogManager.getLog( ElementEventHandlerMockImpl.class );
-
-    /** ELEMENT_EVENT_SPOOLED_DISK_AVAILABLE */
-    private int spoolCount = 0;
-
-    /** ELEMENT_EVENT_SPOOLED_NOT_ALLOWED */
-    private int spoolNotAllowedCount = 0;
-
-    /** ELEMENT_EVENT_SPOOLED_DISK_NOT_AVAILABLE */
-    private int spoolNoDiskCount = 0;
-
-    /** ELEMENT_EVENT_EXCEEDED_MAXLIFE_BACKGROUND */
-    private int exceededMaxLifeBackgroundCount = 0;
-
-    /** ELEMENT_EVENT_EXCEEDED_IDLETIME_BACKGROUND */
-    private int exceededIdleTimeBackgroundCount = 0;
-
-    /**
-     * @param event
-     */
-    @Override
-    public synchronized <T> void handleElementEvent( IElementEvent<T> event )
-    {
-        setCallCount( getCallCount() + 1 );
-
-        if ( log.isDebugEnabled() )
-        {
-            log.debug( "HANDLER -- HANDLER -- HANDLER -- ---EVENT CODE = " + event.getElementEvent() );
-            log.debug( "/n/n EVENT CODE = " + event.getElementEvent() + " ***************************" );
-        }
-
-        if ( event.getElementEvent() == ElementEventType.SPOOLED_DISK_AVAILABLE )
-        {
-            setSpoolCount( getSpoolCount() + 1 );
-        }
-        else if ( event.getElementEvent() == ElementEventType.SPOOLED_NOT_ALLOWED )
-        {
-            setSpoolNotAllowedCount( getSpoolNotAllowedCount() + 1 );
-        }
-        else if ( event.getElementEvent() == ElementEventType.SPOOLED_DISK_NOT_AVAILABLE )
-        {
-            setSpoolNoDiskCount( getSpoolNoDiskCount() + 1 );
-        }
-        else if ( event.getElementEvent() == ElementEventType.EXCEEDED_MAXLIFE_BACKGROUND )
-        {
-            setExceededMaxLifeBackgroundCount( getExceededMaxLifeBackgroundCount() + 1 );
-        }
-        else if ( event.getElementEvent() == ElementEventType.EXCEEDED_IDLETIME_BACKGROUND )
-        {
-            setExceededIdleTimeBackgroundCount( getExceededIdleTimeBackgroundCount() + 1 );
-        }
-    }
-
-    /**
-     * @param spoolCount The spoolCount to set.
-     */
-    public void setSpoolCount( int spoolCount )
-    {
-        this.spoolCount = spoolCount;
-    }
-
-    /**
-     * @return Returns the spoolCount.
-     */
-    public int getSpoolCount()
-    {
-        return spoolCount;
-    }
-
-    /**
-     * @param spoolNotAllowedCount The spoolNotAllowedCount to set.
-     */
-    public void setSpoolNotAllowedCount( int spoolNotAllowedCount )
-    {
-        this.spoolNotAllowedCount = spoolNotAllowedCount;
-    }
-
-    /**
-     * @return Returns the spoolNotAllowedCount.
-     */
-    public int getSpoolNotAllowedCount()
-    {
-        return spoolNotAllowedCount;
-    }
-
-    /**
-     * @param spoolNoDiskCount The spoolNoDiskCount to set.
-     */
-    public void setSpoolNoDiskCount( int spoolNoDiskCount )
-    {
-        this.spoolNoDiskCount = spoolNoDiskCount;
-    }
-
-    /**
-     * @return Returns the spoolNoDiskCount.
-     */
-    public int getSpoolNoDiskCount()
-    {
-        return spoolNoDiskCount;
-    }
-
-    /**
-     * @param exceededMaxLifeBackground The exceededMaxLifeBackground to set.
-     */
-    public void setExceededMaxLifeBackgroundCount( int exceededMaxLifeBackground )
-    {
-        this.exceededMaxLifeBackgroundCount = exceededMaxLifeBackground;
-    }
-
-    /**
-     * @return Returns the exceededMaxLifeBackground.
-     */
-    public int getExceededMaxLifeBackgroundCount()
-    {
-        return exceededMaxLifeBackgroundCount;
-    }
-
-    /**
-     * @param callCount The callCount to set.
-     */
-    public void setCallCount( int callCount )
-    {
-        this.callCount = callCount;
-    }
-
-    /**
-     * @return Returns the callCount.
-     */
-    public int getCallCount()
-    {
-        return callCount;
-    }
-
-    /**
-     * @param exceededIdleTimeBackground The exceededIdleTimeBackground to set.
-     */
-    public void setExceededIdleTimeBackgroundCount( int exceededIdleTimeBackground )
-    {
-        this.exceededIdleTimeBackgroundCount = exceededIdleTimeBackground;
-    }
-
-    /**
-     * @return Returns the exceededIdleTimeBackground.
-     */
-    public int getExceededIdleTimeBackgroundCount()
-    {
-        return exceededIdleTimeBackgroundCount;
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/control/event/SimpleEventHandlingUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/control/event/SimpleEventHandlingUnitTest.java
deleted file mode 100644
index b4ff401..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/control/event/SimpleEventHandlingUnitTest.java
+++ /dev/null
@@ -1,380 +0,0 @@
-package org.apache.commons.jcs.engine.control.event;
-
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.engine.ElementAttributes;
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-import org.apache.commons.jcs.engine.control.event.behavior.IElementEvent;
-import org.apache.commons.jcs.engine.control.event.behavior.IElementEventHandler;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-
-/**
- * This test suite verifies that the basic ElementEvent are called as they should be.
- */
-public class SimpleEventHandlingUnitTest
-    extends TestCase
-{
-    /** Items to test with */
-    private static int items = 20000;
-
-    /**
-     * Test setup with expected configuration parameters.
-     */
-    @Override
-    public void setUp()
-    {
-        JCS.setConfigFilename( "/TestSimpleEventHandling.ccf" );
-    }
-
-    /**
-     * Verify that the spooled event is called as expected.
-     * <p>
-     * @throws Exception Description of the Exception
-     */
-    public void testSpoolEvent()
-        throws Exception
-    {
-        // SETUP
-        MyEventHandler meh = new MyEventHandler();
-
-        CacheAccess<String, String> jcs = JCS.getInstance( "WithDisk" );
-        // this should add the event handler to all items as they are created.
-        IElementAttributes attributes = jcs.getDefaultElementAttributes();
-        attributes.addElementEventHandler( meh );
-        jcs.setDefaultElementAttributes( attributes );
-
-        // DO WORK
-        // put them in
-        for ( int i = 0; i <= items; i++ )
-        {
-            jcs.put( i + ":key", "data" + i );
-        }
-
-        // wait a bit for it to finish
-        Thread.sleep( items / 20 );
-
-        // VERIFY
-        // test to see if the count is right
-        assertTrue( "The number of ELEMENT_EVENT_SPOOLED_DISK_AVAILABLE events [" + meh.getSpoolCount()
-            + "] does not equal the number expected [" + items + "]", meh.getSpoolCount() >= items );
-    }
-
-    /**
-     * Test overflow with no disk configured for the region.
-     * <p>
-     * @throws Exception
-     */
-    public void testSpoolNoDiskEvent()
-        throws Exception
-    {
-        CacheAccess<String, String> jcs = JCS.getInstance( "NoDisk" );
-
-        MyEventHandler meh = new MyEventHandler();
-
-        // this should add the event handler to all items as they are created.
-        IElementAttributes attributes = jcs.getDefaultElementAttributes();
-        attributes.addElementEventHandler( meh );
-        jcs.setDefaultElementAttributes( attributes );
-
-        // put them in
-        for ( int i = 0; i <= items; i++ )
-        {
-            jcs.put( i + ":key", "data" + i );
-        }
-
-        // wait a bit for it to finish
-        Thread.sleep( items / 20 );
-
-        // test to see if the count is right
-        assertTrue( "The number of ELEMENT_EVENT_SPOOLED_DISK_NOT_AVAILABLE events  [" + meh.getSpoolNoDiskCount()
-            + "] does not equal the number expected.", meh.getSpoolNoDiskCount() >= items );
-
-    }
-
-    /**
-     * Test the ELEMENT_EVENT_SPOOLED_NOT_ALLOWED event.
-     * @throws Exception
-     */
-    public void testSpoolNotAllowedEvent()
-        throws Exception
-    {
-        MyEventHandler meh = new MyEventHandler();
-
-        CacheAccess<String, String> jcs = JCS.getInstance( "DiskButNotAllowed" );
-        // this should add the event handler to all items as they are created.
-        IElementAttributes attributes = jcs.getDefaultElementAttributes();
-        attributes.addElementEventHandler( meh );
-        jcs.setDefaultElementAttributes( attributes );
-
-        // put them in
-        for ( int i = 0; i <= items; i++ )
-        {
-            jcs.put( i + ":key", "data" + i );
-        }
-
-        // wait a bit for it to finish
-        Thread.sleep( items / 20 );
-
-        // test to see if the count is right
-        assertTrue( "The number of ELEMENT_EVENT_SPOOLED_NOT_ALLOWED events [" + meh.getSpoolNotAllowedCount()
-            + "] does not equal the number expected.", meh.getSpoolNotAllowedCount() >= items );
-
-    }
-
-    /**
-     * Test the ELEMENT_EVENT_SPOOLED_NOT_ALLOWED event.
-     * @throws Exception
-     */
-    public void testSpoolNotAllowedEventOnItem()
-        throws Exception
-    {
-        MyEventHandler meh = new MyEventHandler();
-
-        CacheAccess<String, String> jcs = JCS.getInstance( "DiskButNotAllowed" );
-        // this should add the event handler to all items as they are created.
-        //IElementAttributes attributes = jcs.getDefaultElementAttributes();
-        //attributes.addElementEventHandler( meh );
-        //jcs.setDefaultElementAttributes( attributes );
-
-        // put them in
-        for ( int i = 0; i <= items; i++ )
-        {
-            IElementAttributes attributes = jcs.getDefaultElementAttributes();
-            attributes.addElementEventHandler( meh );
-            jcs.put( i + ":key", "data" + i, attributes );
-        }
-
-        // wait a bit for it to finish
-        Thread.sleep( items / 20 );
-
-        // test to see if the count is right
-        assertTrue( "The number of ELEMENT_EVENT_SPOOLED_NOT_ALLOWED events [" + meh.getSpoolNotAllowedCount()
-            + "] does not equal the number expected.", meh.getSpoolNotAllowedCount() >= items );
-
-    }
-
-    /**
-     * Test the ELEMENT_EVENT_EXCEEDED_MAXLIFE_ONREQUEST event.
-     * @throws Exception
-     */
-    public void testExceededMaxlifeOnrequestEvent()
-        throws Exception
-    {
-        MyEventHandler meh = new MyEventHandler();
-
-        CacheAccess<String, String> jcs = JCS.getInstance( "Maxlife" );
-        // this should add the event handler to all items as they are created.
-        IElementAttributes attributes = jcs.getDefaultElementAttributes();
-        attributes.addElementEventHandler( meh );
-        jcs.setDefaultElementAttributes( attributes );
-
-        // put them in
-        for ( int i = 0; i < 200; i++ )
-        {
-            jcs.put( i + ":key", "data" + i);
-        }
-
-        // wait a bit for the items to expire
-        Thread.sleep( 3000 );
-
-        for ( int i = 0; i < 200; i++ )
-        {
-            String value = jcs.get( i + ":key");
-            assertNull("Item should be null for key " + i + ":key, but is " + value, value);
-        }
-
-        // wait a bit for it to finish
-        Thread.sleep( 100 );
-
-        // test to see if the count is right
-        assertTrue( "The number of ELEMENT_EVENT_EXCEEDED_MAXLIFE_ONREQUEST events [" + meh.getExceededMaxlifeCount()
-            + "] does not equal the number expected.", meh.getExceededMaxlifeCount() >= 200 );
-    }
-
-    /**
-     * Test the ELEMENT_EVENT_EXCEEDED_IDLETIME_ONREQUEST event.
-     * @throws Exception
-     */
-    public void testExceededIdletimeOnrequestEvent()
-        throws Exception
-    {
-        MyEventHandler meh = new MyEventHandler();
-
-        CacheAccess<String, String> jcs = JCS.getInstance( "Idletime" );
-        // this should add the event handler to all items as they are created.
-        IElementAttributes attributes = jcs.getDefaultElementAttributes();
-        attributes.addElementEventHandler( meh );
-        jcs.setDefaultElementAttributes( attributes );
-
-        // put them in
-        for ( int i = 0; i < 200; i++ )
-        {
-            jcs.put( i + ":key", "data" + i);
-        }
-
-        // update access time
-        for ( int i = 0; i < 200; i++ )
-        {
-            String value = jcs.get( i + ":key");
-            assertNotNull("Item should not be null for key " + i + ":key", value);
-        }
-
-        // wait a bit for the items to expire
-        Thread.sleep( 1500 );
-
-        for ( int i = 0; i < 200; i++ )
-        {
-            String value = jcs.get( i + ":key");
-            assertNull("Item should be null for key " + i + ":key, but is " + value, value);
-        }
-
-        // wait a bit for it to finish
-        Thread.sleep( 100 );
-
-        // test to see if the count is right
-        assertTrue( "The number of ELEMENT_EVENT_EXCEEDED_IDLETIME_ONREQUEST events [" + meh.getExceededIdletimeCount()
-            + "] does not equal the number expected.", meh.getExceededIdletimeCount() >= 200 );
-    }
-
-    /**
-     * Test that cloned ElementAttributes have different creation times.
-     * @throws Exception
-     */
-    public void testElementAttributesCreationTime()
-        throws Exception
-    {
-    	ElementAttributes elem1 = new ElementAttributes();
-    	long ctime1 = elem1.getCreateTime();
-    	
-    	Thread.sleep(10);
-
-    	IElementAttributes elem2 = elem1.clone();
-    	long ctime2 = elem2.getCreateTime();
-    	
-    	assertFalse("Creation times should be different", ctime1 == ctime2);
-    }
-    
-    /**
-     * Simple event counter used to verify test results.
-     */
-    public static class MyEventHandler
-        implements IElementEventHandler
-    {
-        /** times spool called */
-        private int spoolCount = 0;
-
-        /** times spool not allowed */
-        private int spoolNotAllowedCount = 0;
-
-        /** times spool without disk */
-        private int spoolNoDiskCount = 0;
-
-        /** times exceeded maxlife */
-        private int exceededMaxlifeCount = 0;
-
-        /** times exceeded idle time */
-        private int exceededIdletimeCount = 0;
-
-        /**
-         * @param event
-         */
-        @Override
-        public synchronized <T> void handleElementEvent( IElementEvent<T> event )
-        {
-            //System.out.println( "Handling Event of Type " +
-            // event.getElementEvent() );
-
-            switch (event.getElementEvent())
-            {
-                case SPOOLED_DISK_AVAILABLE:
-                //System.out.println( "Handling Event of Type
-                // ELEMENT_EVENT_SPOOLED_DISK_AVAILABLE, " + getSpoolCount() );
-                spoolCount++;
-                break;
-
-                case SPOOLED_NOT_ALLOWED:
-                spoolNotAllowedCount++;
-                break;
-
-                case SPOOLED_DISK_NOT_AVAILABLE:
-                spoolNoDiskCount++;
-                break;
-
-                case EXCEEDED_MAXLIFE_ONREQUEST:
-                exceededMaxlifeCount++;
-                break;
-
-                case EXCEEDED_IDLETIME_ONREQUEST:
-                exceededIdletimeCount++;
-                break;
-
-                case EXCEEDED_IDLETIME_BACKGROUND:
-                break;
-
-                case EXCEEDED_MAXLIFE_BACKGROUND:
-                break;
-            }
-        }
-
-        /**
-         * @return Returns the spoolCount.
-         */
-        protected int getSpoolCount()
-        {
-            return spoolCount;
-        }
-
-        /**
-         * @return Returns the spoolNotAllowedCount.
-         */
-        protected int getSpoolNotAllowedCount()
-        {
-            return spoolNotAllowedCount;
-        }
-
-        /**
-         * @return Returns the spoolNoDiskCount.
-         */
-        protected int getSpoolNoDiskCount()
-        {
-            return spoolNoDiskCount;
-        }
-
-        /**
-         * @return the exceededMaxlifeCount
-         */
-        protected int getExceededMaxlifeCount()
-        {
-            return exceededMaxlifeCount;
-        }
-
-        /**
-         * @return the exceededIdletimeCount
-         */
-        protected int getExceededIdletimeCount()
-        {
-            return exceededIdletimeCount;
-        }
-    }
-
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/logging/CacheEventLoggerDebugLoggerUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/logging/CacheEventLoggerDebugLoggerUnitTest.java
deleted file mode 100644
index ebfb696..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/logging/CacheEventLoggerDebugLoggerUnitTest.java
+++ /dev/null
@@ -1,117 +0,0 @@
-package org.apache.commons.jcs.engine.logging;
-
-import java.io.StringWriter;
-
-import org.apache.commons.jcs.TestLogConfigurationUtil;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEvent;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-
-/** Unit tests for the debug implementation */
-public class CacheEventLoggerDebugLoggerUnitTest
-    extends TestCase
-{
-
-    /** verify that we can log */
-    public void testLogICacheEvent_normal()
-    {
-        // SETUP
-        String logCategoryName = "testLogEvent_normal";
-
-        String source = "mySource";
-        String region = "my region";
-        String eventName = "MyEventName";
-        String optionalDetails = "SomeExtraData";
-        String key = "my key";
-
-        StringWriter stringWriter = new StringWriter();
-        TestLogConfigurationUtil.configureLogger( stringWriter, logCategoryName );
-
-        CacheEventLoggerDebugLogger logger = new CacheEventLoggerDebugLogger();
-        logger.setLogCategoryName( logCategoryName );
-
-        ICacheEvent<String> event = logger.createICacheEvent( source, region, eventName, optionalDetails, key );
-
-        // DO WORK
-        logger.logICacheEvent( event );
-
-        // VERIFY
-        String result = stringWriter.toString();
-        assertTrue( "An event with the source should have been logged:" + result, result.indexOf( source ) != -1 );
-        assertTrue( "An event with the region should have been logged:" + result, result.indexOf( region ) != -1 );
-        assertTrue( "An event with the event name should have been logged:" + result, result.indexOf( eventName ) != -1 );
-        assertTrue( "An event with the optionalDetails should have been logged:" + result, result.indexOf( optionalDetails ) != -1 );
-        assertTrue( "An event with the key should have been logged:" + result, result.indexOf( key ) != -1 );
-    }
-
-    /** verify that we can log */
-    public void testLogApplicationEvent_normal()
-    {
-        // SETUP
-        String logCategoryName = "testLogApplicationEvent_normal";
-
-        String source = "mySource";
-        String eventName = "MyEventName";
-        String optionalDetails = "SomeExtraData";
-
-        StringWriter stringWriter = new StringWriter();
-        TestLogConfigurationUtil.configureLogger( stringWriter, logCategoryName );
-
-        CacheEventLoggerDebugLogger logger = new CacheEventLoggerDebugLogger();
-        logger.setLogCategoryName( logCategoryName );
-
-        // DO WORK
-        logger.logApplicationEvent( source, eventName, optionalDetails );
-
-        // VERIFY
-        String result = stringWriter.toString();
-        assertTrue( "An event with the source should have been logged:" + result, result.indexOf( source ) != -1 );
-        assertTrue( "An event with the event name should have been logged:" + result, result.indexOf( eventName ) != -1 );
-        assertTrue( "An event with the optionalDetails should have been logged:" + result, result.indexOf( optionalDetails ) != -1 );
-    }
-
-    /** verify that we can log */
-    public void testLogError_normal()
-    {
-        // SETUP
-        String logCategoryName = "testLogApplicationEvent_normal";
-
-        String source = "mySource";
-        String eventName = "MyEventName";
-        String errorMessage = "SomeExtraData";
-
-        StringWriter stringWriter = new StringWriter();
-        TestLogConfigurationUtil.configureLogger( stringWriter, logCategoryName );
-
-        CacheEventLoggerDebugLogger logger = new CacheEventLoggerDebugLogger();
-        logger.setLogCategoryName( logCategoryName );
-
-        // DO WORK
-        logger.logError( source, eventName, errorMessage );
-
-        // VERIFY
-        String result = stringWriter.toString();
-        assertTrue( "An event with the source should have been logged:" + result, result.indexOf( source ) != -1 );
-        assertTrue( "An event with the event name should have been logged:" + result, result.indexOf( eventName ) != -1 );
-        assertTrue( "An event with the errorMessage should have been logged:" + result, result.indexOf( errorMessage ) != -1 );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/logging/MockCacheEventLogger.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/logging/MockCacheEventLogger.java
deleted file mode 100644
index b2768c6..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/logging/MockCacheEventLogger.java
+++ /dev/null
@@ -1,95 +0,0 @@
-package org.apache.commons.jcs.engine.logging;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEvent;
-import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
-
-/**
- * For testing the configurator.
- */
-public class MockCacheEventLogger
-    implements ICacheEventLogger
-{
-    /** test property */
-    private String testProperty;
-
-    /**
-     * @param source
-     * @param eventName
-     * @param optionalDetails
-     */
-    @Override
-    public void logApplicationEvent( String source, String eventName, String optionalDetails )
-    {
-        // TODO Auto-generated method stub
-    }
-
-    /**
-     * @param source
-     * @param eventName
-     * @param errorMessage
-     */
-    @Override
-    public void logError( String source, String eventName, String errorMessage )
-    {
-        // TODO Auto-generated method stub
-    }
-
-    /**
-     * @param source
-     * @param region
-     * @param eventName
-     * @param optionalDetails
-     * @param key
-     * @return ICacheEvent
-     */
-    @Override
-    public <T> ICacheEvent<T> createICacheEvent( String source, String region, String eventName, String optionalDetails,
-                                          T key )
-    {
-        return new CacheEvent<>();
-    }
-
-    /**
-     * @param event
-     */
-    @Override
-    public <T> void logICacheEvent( ICacheEvent<T> event )
-    {
-        // TODO Auto-generated method stub
-    }
-
-    /**
-     * @param testProperty
-     */
-    public void setTestProperty( String testProperty )
-    {
-        this.testProperty = testProperty;
-    }
-
-    /**
-     * @return testProperty
-     */
-    public String getTestProperty()
-    {
-        return testProperty;
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/match/KeyMatcherPatternImpllUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/match/KeyMatcherPatternImpllUnitTest.java
deleted file mode 100644
index 9847b62..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/match/KeyMatcherPatternImpllUnitTest.java
+++ /dev/null
@@ -1,118 +0,0 @@
-package org.apache.commons.jcs.engine.match;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/** Unit tests for the key matcher. */
-public class KeyMatcherPatternImpllUnitTest
-    extends TestCase
-{
-    /**
-     * Verify that the matching method works.
-     */
-    public void testGetMatchingKeysFromArray_AllMatch()
-    {
-        // SETUP
-        int numToInsertPrefix1 = 10;
-        Set<String> keyArray = new HashSet<>();
-
-        String keyprefix1 = "MyPrefixC";
-
-        // insert with prefix1
-        for ( int i = 0; i < numToInsertPrefix1; i++ )
-        {
-            keyArray.add(keyprefix1 + String.valueOf( i ));
-        }
-
-        KeyMatcherPatternImpl<String> keyMatcher = new KeyMatcherPatternImpl<>();
-
-        // DO WORK
-        Set<String> result1 = keyMatcher.getMatchingKeysFromArray( keyprefix1 + ".", keyArray );
-
-        // VERIFY
-        assertEquals( "Wrong number returned 1: " + result1, numToInsertPrefix1, result1.size() );
-    }
-
-    /**
-     * Verify that the matching method works.
-     */
-    public void testGetMatchingKeysFromArray_AllMatchFirstNull()
-    {
-        // SETUP
-        int numToInsertPrefix1 = 10;
-        Set<String> keyArray = new HashSet<>();
-
-        String keyprefix1 = "MyPrefixC";
-
-        // insert with prefix1
-        for ( int i = 1; i < numToInsertPrefix1 + 1; i++ )
-        {
-            keyArray.add(keyprefix1 + String.valueOf( i ));
-        }
-
-        KeyMatcherPatternImpl<String> keyMatcher = new KeyMatcherPatternImpl<>();
-
-        // DO WORK
-        Set<String> result1 = keyMatcher.getMatchingKeysFromArray( keyprefix1 + "\\S+", keyArray );
-
-        // VERIFY
-        assertEquals( "Wrong number returned 1: " + result1, numToInsertPrefix1, result1.size() );
-    }
-
-    /**
-     * Verify that the matching method works.
-     */
-    public void testGetMatchingKeysFromArray_TwoTypes()
-    {
-        // SETUP
-        int numToInsertPrefix1 = 10;
-        int numToInsertPrefix2 = 50;
-        Set<String> keyArray = new HashSet<>();
-
-        String keyprefix1 = "MyPrefixA";
-        String keyprefix2 = "MyPrefixB";
-
-        // insert with prefix1
-        for ( int i = 0; i < numToInsertPrefix1; i++ )
-        {
-            keyArray.add(keyprefix1 + String.valueOf( i ));
-        }
-
-        // insert with prefix2
-        for ( int i = numToInsertPrefix1; i < numToInsertPrefix2 + numToInsertPrefix1; i++ )
-        {
-            keyArray.add(keyprefix2 + String.valueOf( i ));
-        }
-
-        KeyMatcherPatternImpl<String> keyMatcher = new KeyMatcherPatternImpl<>();
-
-        // DO WORK
-        Set<String> result1 = keyMatcher.getMatchingKeysFromArray( keyprefix1 + ".+", keyArray );
-        Set<String> result2 = keyMatcher.getMatchingKeysFromArray( keyprefix2 + ".+", keyArray );
-
-        // VERIFY
-        assertEquals( "Wrong number returned 1: " + result1, numToInsertPrefix1, result1.size() );
-        assertEquals( "Wrong number returned 2: " + result2, numToInsertPrefix2, result2.size() );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/memory/MockMemoryCache.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/memory/MockMemoryCache.java
deleted file mode 100644
index d4ba8ca..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/memory/MockMemoryCache.java
+++ /dev/null
@@ -1,255 +0,0 @@
-package org.apache.commons.jcs.engine.memory;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheAttributes;
-import org.apache.commons.jcs.engine.control.CompositeCache;
-import org.apache.commons.jcs.engine.memory.behavior.IMemoryCache;
-import org.apache.commons.jcs.engine.stats.behavior.IStats;
-
-/**
- * Mock implementation of a memory cache for testing things like the memory shrinker.
- * <p>
- * @author Aaron Smuts
- */
-public class MockMemoryCache<K, V>
-    implements IMemoryCache<K, V>
-{
-    /** Config */
-    private ICompositeCacheAttributes cacheAttr;
-
-    /** Internal map */
-    private final HashMap<K, ICacheElement<K, V>> map = new HashMap<>();
-
-    /** The number of times waterfall was called. */
-    public int waterfallCallCount = 0;
-
-    /** The number passed to the last call of free elements. */
-    public int lastNumberOfFreedElements = 0;
-
-    /**
-     * Does nothing
-     * @param cache
-     */
-    @Override
-    public void initialize( CompositeCache<K, V> cache )
-    {
-        // nothing
-    }
-
-    /**
-     * Destroy the memory cache
-     * <p>
-     * @throws IOException
-     */
-    @Override
-    public void dispose()
-        throws IOException
-    {
-        // nothing
-    }
-
-    /** @return size */
-    @Override
-    public int getSize()
-    {
-        return map.size();
-    }
-
-    /** @return stats */
-    @Override
-    public IStats getStatistics()
-    {
-        return null;
-    }
-
-    /**
-     * @return map.keySet().toArray( */
-    @Override
-    public Set<K> getKeySet()
-    {
-        return new LinkedHashSet<>(map.keySet());
-    }
-
-    /**
-     * @param key
-     * @return map.remove( key ) != null
-     * @throws IOException
-     */
-    @Override
-    public boolean remove( K key )
-        throws IOException
-    {
-        return map.remove( key ) != null;
-    }
-
-    /**
-     * @throws IOException
-     */
-    @Override
-    public void removeAll()
-        throws IOException
-    {
-        map.clear();
-    }
-
-    /**
-     * @param key
-     * @return (ICacheElement) map.get( key )
-     * @throws IOException
-     */
-    @Override
-    public ICacheElement<K, V> get( K key )
-        throws IOException
-    {
-        return map.get( key );
-    }
-
-    /**
-     * @param keys
-     * @return elements
-     * @throws IOException
-     */
-    @Override
-    public Map<K, ICacheElement<K, V>> getMultiple(Set<K> keys)
-        throws IOException
-    {
-        Map<K, ICacheElement<K, V>> elements = new HashMap<>();
-
-        if ( keys != null && !keys.isEmpty() )
-        {
-            Iterator<K> iterator = keys.iterator();
-
-            while ( iterator.hasNext() )
-            {
-                K key = iterator.next();
-
-                ICacheElement<K, V> element = get( key );
-
-                if ( element != null )
-                {
-                    elements.put( key, element );
-                }
-            }
-        }
-
-        return elements;
-    }
-
-    /**
-     * @param key
-     * @return (ICacheElement) map.get( key )
-     * @throws IOException
-     */
-    @Override
-    public ICacheElement<K, V> getQuiet( K key )
-        throws IOException
-    {
-        return map.get( key );
-    }
-
-    /**
-     * @param ce
-     * @throws IOException
-     */
-    @Override
-    public void waterfal( ICacheElement<K, V> ce )
-        throws IOException
-    {
-        waterfallCallCount++;
-    }
-
-    /**
-     * @param ce
-     * @throws IOException
-     */
-    @Override
-    public void update( ICacheElement<K, V> ce )
-        throws IOException
-    {
-        if ( ce != null )
-        {
-            map.put( ce.getKey(), ce );
-        }
-    }
-
-    /**
-     * @return ICompositeCacheAttributes
-     */
-    @Override
-    public ICompositeCacheAttributes getCacheAttributes()
-    {
-        return cacheAttr;
-    }
-
-    /**
-     * @param cattr
-     */
-    @Override
-    public void setCacheAttributes( ICompositeCacheAttributes cattr )
-    {
-        this.cacheAttr = cattr;
-    }
-
-    /** @return null */
-    @Override
-    public CompositeCache<K, V> getCompositeCache()
-    {
-        return null;
-    }
-
-    /**
-     * @param group
-     * @return null
-     */
-    public Set<K> getGroupKeys( String group )
-    {
-        return null;
-    }
-
-    /**
-     * @return null
-     */
-    public Set<String> getGroupNames()
-    {
-        return null;
-    }
-
-    /**
-     * @param numberToFree
-     * @return 0
-     * @throws IOException
-     */
-    @Override
-    public int freeElements( int numberToFree )
-        throws IOException
-    {
-        lastNumberOfFreedElements = numberToFree;
-        return 0;
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/memory/fifo/FIFOMemoryCacheUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/memory/fifo/FIFOMemoryCacheUnitTest.java
deleted file mode 100644
index 35b6cff..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/memory/fifo/FIFOMemoryCacheUnitTest.java
+++ /dev/null
@@ -1,111 +0,0 @@
-package org.apache.commons.jcs.engine.memory.fifo;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-
-import junit.framework.TestCase;
-
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.CompositeCacheAttributes;
-import org.apache.commons.jcs.engine.ElementAttributes;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheAttributes;
-import org.apache.commons.jcs.engine.control.CompositeCache;
-
-/** Unit tests for the fifo implementation. */
-public class FIFOMemoryCacheUnitTest
-    extends TestCase
-{
-    /**
-     * Verify that the oldest inserted item is removed
-     * <p>
-     * @throws IOException
-     */
-    public void testExpirationPolicy_oneExtra()
-        throws IOException
-    {
-        // SETUP
-        int maxObjects = 10;
-        String cacheName = "testExpirationPolicy_oneExtra";
-
-        ICompositeCacheAttributes attributes = new CompositeCacheAttributes();
-        attributes.setCacheName(cacheName);
-        attributes.setMaxObjects( maxObjects );
-        attributes.setSpoolChunkSize( 1 );
-
-        FIFOMemoryCache<String, String> cache = new FIFOMemoryCache<>();
-        cache.initialize( new CompositeCache<>( attributes, new ElementAttributes() ) );
-
-        for ( int i = 0; i <= maxObjects; i++ )
-        {
-            CacheElement<String, String> element = new CacheElement<>( cacheName, "key" + i, "value" + i );
-            cache.update( element );
-        }
-
-        CacheElement<String, String> oneMoreElement = new CacheElement<>( cacheName, "onemore", "onemore" );
-
-        // DO WORK
-        cache.update( oneMoreElement );
-
-        // VERIFY
-        assertEquals( "Should have max elements", maxObjects, cache.getSize() );
-        System.out.println(cache.getKeySet());
-        for ( int i = maxObjects; i > 1; i-- )
-        {
-            assertNotNull( "Should have element " + i, cache.get( "key" + i ) );
-        }
-        assertNotNull( "Should have oneMoreElement", cache.get( "onemore" ) );
-    }
-
-    /**
-     * Verify that the oldest inserted item is removed
-     * <p>
-     * @throws IOException
-     */
-    public void testExpirationPolicy_doubleOver()
-        throws IOException
-    {
-        // SETUP
-        int maxObjects = 10;
-        String cacheName = "testExpirationPolicy_oneExtra";
-
-        ICompositeCacheAttributes attributes = new CompositeCacheAttributes();
-        attributes.setCacheName(cacheName);
-        attributes.setMaxObjects( maxObjects );
-        attributes.setSpoolChunkSize( 1 );
-
-        FIFOMemoryCache<String, String> cache = new FIFOMemoryCache<>();
-        cache.initialize( new CompositeCache<>( attributes, new ElementAttributes() ) );
-
-        // DO WORK
-        for ( int i = 0; i <= (maxObjects * 2); i++ )
-        {
-            CacheElement<String, String> element = new CacheElement<>( cacheName, "key" + i, "value" + i );
-            cache.update( element );
-        }
-
-        // VERIFY
-        assertEquals( "Should have max elements", maxObjects, cache.getSize() );
-        for ( int i = (maxObjects * 2); i > maxObjects; i-- )
-        {
-            assertNotNull( "Shjould have elemnt " + i, cache.get( "key" + i ) );
-        }
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/memory/lru/LHMLRUMemoryCacheConcurrentUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/memory/lru/LHMLRUMemoryCacheConcurrentUnitTest.java
deleted file mode 100644
index e981456..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/memory/lru/LHMLRUMemoryCacheConcurrentUnitTest.java
+++ /dev/null
@@ -1,175 +0,0 @@
-package org.apache.commons.jcs.engine.memory.lru;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.extensions.ActiveTestSuite;
-import junit.framework.Test;
-import junit.framework.TestCase;
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.control.CompositeCache;
-import org.apache.commons.jcs.engine.control.CompositeCacheManager;
-
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Test which exercises the LRUMemory cache. This one uses three different
- * regions for three threads.
- */
-public class LHMLRUMemoryCacheConcurrentUnitTest
-    extends TestCase
-{
-    /**
-     * Number of items to cache, twice the configured maxObjects for the memory
-     * cache regions.
-     */
-    private static int items = 200;
-
-    /**
-     * Constructor for the TestDiskCache object.
-     *
-     * @param testName
-     */
-    public LHMLRUMemoryCacheConcurrentUnitTest( String testName )
-    {
-        super( testName );
-    }
-
-    /**
-     * Main method passes this test to the text test runner.
-     *
-     * @param args
-     */
-    public static void main( String args[] )
-    {
-        String[] testCaseName = { LHMLRUMemoryCacheConcurrentUnitTest.class.getName() };
-        junit.textui.TestRunner.main( testCaseName );
-    }
-
-    /**
-     * A unit test suite for JUnit
-     * <p>
-     * @return The test suite
-     */
-    public static Test suite()
-    {
-        ActiveTestSuite suite = new ActiveTestSuite();
-
-        suite.addTest( new LHMLRUMemoryCacheConcurrentUnitTest( "testLHMLRUMemoryCache" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "indexedRegion1" );
-            }
-        } );
-
-        return suite;
-    }
-
-    /**
-     * Test setup
-     */
-    @Override
-    public void setUp()
-    {
-        //JCS.setConfigFilename( "/TestLHMLRUCache.ccf" );
-    }
-
-    /**
-     * Adds items to cache, gets them, and removes them. The item count is more
-     * than the size of the memory cache, so items should be dumped.
-     * <p>
-     * @param region
-     *            Name of the region to access
-     *
-     * @throws Exception
-     *                If an error occurs
-     */
-    public void runTestForRegion( String region )
-        throws Exception
-    {
-        CompositeCacheManager cacheMgr = CompositeCacheManager.getUnconfiguredInstance();
-        cacheMgr.configure( "/TestLHMLRUCache.ccf" );
-        CompositeCache<String, String> cache = cacheMgr.getCache( region );
-
-        LRUMemoryCache<String, String> lru = new LRUMemoryCache<>();
-        lru.initialize( cache );
-
-        // Add items to cache
-
-        for ( int i = 0; i < items; i++ )
-        {
-            ICacheElement<String, String> ice = new CacheElement<>( cache.getCacheName(), i + ":key", region + " data " + i );
-            ice.setElementAttributes( cache.getElementAttributes() );
-            lru.update( ice );
-        }
-
-        // Test that initial items have been purged
-        for ( int i = 0; i < 100; i++ )
-        {
-            assertNull( "Should not have " + i + ":key", lru.get( i + ":key" ) );
-        }
-
-        // Test that last items are in cache
-        for ( int i = 100; i < items; i++ )
-        {
-            String value = lru.get( i + ":key" ).getVal();
-            assertEquals( region + " data " + i, value );
-        }
-
-        // Test that getMultiple returns all the items remaining in cache and none of the missing ones
-        Set<String> keys = new HashSet<>();
-        for ( int i = 0; i < items; i++ )
-        {
-            keys.add( i + ":key" );
-        }
-
-        Map<String, ICacheElement<String, String>> elements = lru.getMultiple( keys );
-        for ( int i = 0; i < 100; i++ )
-        {
-            assertNull( "Should not have " + i + ":key", elements.get( i + ":key" ) );
-        }
-        for ( int i = 100; i < items; i++ )
-        {
-            ICacheElement<String, String> element = elements.get( i + ":key" );
-            assertNotNull( "element " + i + ":key is missing", element );
-            assertEquals( "value " + i + ":key", region + " data " + i, element.getVal() );
-        }
-
-        // Remove all the items
-
-        for ( int i = 0; i < items; i++ )
-        {
-            lru.remove( i + ":key" );
-        }
-
-        // Verify removal
-
-        for ( int i = 0; i < items; i++ )
-        {
-            assertNull( "Removed key should be null: " + i + ":key", lru.get( i + ":key" ) );
-        }
-    }
-
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/memory/lru/LHMLRUMemoryCacheUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/memory/lru/LHMLRUMemoryCacheUnitTest.java
deleted file mode 100644
index 03adb35..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/memory/lru/LHMLRUMemoryCacheUnitTest.java
+++ /dev/null
@@ -1,312 +0,0 @@
-package org.apache.commons.jcs.engine.memory.lru;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.access.exception.CacheException;
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.control.CompositeCache;
-import org.apache.commons.jcs.engine.control.CompositeCacheManager;
-
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Tests for the test LHMLRU implementation.
- * <p>
- * @author Aaron Smuts
- */
-public class LHMLRUMemoryCacheUnitTest
-    extends TestCase
-{
-    /** Test setup */
-    @Override
-    public void setUp()
-    {
-        JCS.setConfigFilename( "/TestLHMLRUCache.ccf" );
-    }
-
-    /**
-     * Verify that the mru gets used by a non-defined region when it is set as the default in the
-     * default region.
-     * <p>
-     * @throws CacheException
-     */
-    public void testLoadFromCCF()
-        throws CacheException
-    {
-        CacheAccess<String, String> cache = JCS.getInstance( "testLoadFromCCF" );
-        String memoryCacheName = cache.getCacheAttributes().getMemoryCacheName();
-        assertTrue( "Cache name should have LHMLRU in it.", memoryCacheName.indexOf( "LHMLRUMemoryCache" ) != -1 );
-    }
-
-    /**
-     * put twice as many as the max.  verify that the second half is in the cache.
-     * <p>
-     * @throws CacheException
-     */
-    public void testPutGetThroughHub()
-        throws CacheException
-    {
-        CacheAccess<String, String> cache = JCS.getInstance( "testPutGetThroughHub" );
-
-        int max = cache.getCacheAttributes().getMaxObjects();
-        int items = max * 2;
-
-        for ( int i = 0; i < items; i++ )
-        {
-            cache.put( i + ":key", "myregion" + " data " + i );
-        }
-
-        // Test that first items are not in the cache
-        for ( int i = max -1; i >= 0; i-- )
-        {
-            String value = cache.get( i + ":key" );
-            assertNull( "Should not have value for key [" + i + ":key" + "] in the cache." + cache.getStats(), value );
-        }
-
-        // Test that last items are in cache
-        // skip 2 for the buffer.
-        for ( int i = max + 2; i < items; i++ )
-        {
-            String value = cache.get( i + ":key" );
-            assertEquals( "myregion" + " data " + i, value );
-        }
-
-        // Test that getMultiple returns all the items remaining in cache and none of the missing ones
-        Set<String> keys = new HashSet<>();
-        for ( int i = 0; i < items; i++ )
-        {
-            keys.add( i + ":key" );
-        }
-
-        Map<String, ICacheElement<String, String>> elements = cache.getCacheElements( keys );
-        for ( int i = max-1; i >= 0; i-- )
-        {
-            assertNull( "Should not have value for key [" + i + ":key" + "] in the cache." + cache.getStats(), elements.get( i + ":key" ) );
-        }
-        for ( int i = max + 2; i < items; i++ )
-        {
-            ICacheElement<String, String> element = elements.get( i + ":key" );
-            assertNotNull( "element " + i + ":key is missing", element );
-            assertEquals( "value " + i + ":key", "myregion" + " data " + i, element.getVal() );
-        }
-    }
-
-    /**
-     * Put twice as many as the max, twice. verify that the second half is in the cache.
-     * <p>
-     * @throws CacheException
-     */
-    public void testPutGetThroughHubTwice()
-        throws CacheException
-    {
-        CacheAccess<String, String> cache = JCS.getInstance( "testPutGetThroughHubTwice" );
-
-        int max = cache.getCacheAttributes().getMaxObjects();
-        int items = max * 2;
-
-        for ( int i = 0; i < items; i++ )
-        {
-            cache.put( i + ":key", "myregion" + " data " + i );
-        }
-
-        for ( int i = 0; i < items; i++ )
-        {
-            cache.put( i + ":key", "myregion" + " data " + i );
-        }
-
-        // Test that first items are not in the cache
-        for ( int i = max -1; i >= 0; i-- )
-        {
-            String value = cache.get( i + ":key" );
-            assertNull( "Should not have value for key [" + i + ":key" + "] in the cache.", value );
-        }
-
-        // Test that last items are in cache
-        // skip 2 for the buffer.
-        for ( int i = max + 2; i < items; i++ )
-        {
-            String value = cache.get( i + ":key" );
-            assertEquals( "myregion" + " data " + i, value );
-        }
-
-    }
-
-    /**
-     * put the max and remove each. verify that they are all null.
-     * <p>
-     * @throws CacheException
-     */
-    public void testPutRemoveThroughHub()
-        throws CacheException
-    {
-        CacheAccess<String, String> cache = JCS.getInstance( "testPutRemoveThroughHub" );
-
-        int max = cache.getCacheAttributes().getMaxObjects();
-        int items = max * 2;
-
-        for ( int i = 0; i < items; i++ )
-        {
-            cache.put( i + ":key", "myregion" + " data " + i );
-        }
-
-        for ( int i = 0; i < items; i++ )
-        {
-            cache.remove( i + ":key" );
-        }
-
-        // Test that first items are not in the cache
-        for ( int i = max; i >= 0; i-- )
-        {
-            String value = cache.get( i + ":key" );
-            assertNull( "Should not have value for key [" + i + ":key" + "] in the cache.", value );
-        }
-    }
-
-    /**
-     * put the max and clear. verify that no elements remain.
-     * <p>
-     * @throws CacheException
-     */
-    public void testClearThroughHub()
-        throws CacheException
-    {
-        CacheAccess<String, String> cache = JCS.getInstance( "testClearThroughHub" );
-
-        int max = cache.getCacheAttributes().getMaxObjects();
-        int items = max * 2;
-
-        for ( int i = 0; i < items; i++ )
-        {
-            cache.put( i + ":key", "myregion" + " data " + i );
-        }
-
-        cache.clear();
-
-        // Test that first items are not in the cache
-        for ( int i = max; i >= 0; i-- )
-        {
-            String value = cache.get( i + ":key" );
-            assertNull( "Should not have value for key [" + i + ":key" + "] in the cache.", value );
-        }
-    }
-
-    /**
-     * Get stats.
-     * <p>
-     * @throws CacheException
-     */
-    public void testGetStatsThroughHub()
-        throws CacheException
-    {
-        CacheAccess<String, String> cache = JCS.getInstance( "testGetStatsThroughHub" );
-
-        int max = cache.getCacheAttributes().getMaxObjects();
-        int items = max * 2;
-
-        for ( int i = 0; i < items; i++ )
-        {
-            cache.put( i + ":key", "myregion" + " data " + i );
-        }
-
-        String stats = cache.getStats();
-
-        //System.out.println( stats );
-
-        // TODO improve stats check
-        assertTrue( "Should have 200 puts" + stats, stats.indexOf( "200" ) != -1 );
-    }
-
-    /**
-     * Put half the max and clear. get the key array and verify that it has the correct number of
-     * items.
-     * <p>
-     * @throws Exception
-     */
-    public void testGetKeyArray()
-        throws Exception
-    {
-        CompositeCacheManager cacheMgr = CompositeCacheManager.getUnconfiguredInstance();
-        cacheMgr.configure( "/TestLHMLRUCache.ccf" );
-        CompositeCache<String, String> cache = cacheMgr.getCache( "testGetKeyArray" );
-
-        LHMLRUMemoryCache<String, String> mru = new LHMLRUMemoryCache<>();
-        mru.initialize( cache );
-
-        int max = cache.getCacheAttributes().getMaxObjects();
-        int items = max / 2;
-
-        for ( int i = 0; i < items; i++ )
-        {
-            ICacheElement<String, String> ice = new CacheElement<>( cache.getCacheName(), i + ":key", cache.getCacheName() + " data " + i );
-            ice.setElementAttributes( cache.getElementAttributes() );
-            mru.update( ice );
-        }
-
-        Set<String> keys = mru.getKeySet();
-
-        assertEquals( "Wrong number of keys.", items, keys.size() );
-    }
-
-    /**
-     * Add a few keys with the delimiter. Remove them.
-     * <p>
-     * @throws CacheException
-     */
-    public void testRemovePartialThroughHub()
-        throws CacheException
-    {
-        CacheAccess<String, String> cache = JCS.getInstance( "testRemovePartialThroughHub" );
-
-        int max = cache.getCacheAttributes().getMaxObjects();
-        int items = max / 2;
-
-        cache.put( "test", "data" );
-
-        String root = "myroot";
-
-        for ( int i = 0; i < items; i++ )
-        {
-            cache.put( root + ":" + i + ":key", "myregion" + " data " + i );
-        }
-
-        // Test that last items are in cache
-        for ( int i = 0; i < items; i++ )
-        {
-            String value = cache.get( root + ":" + i + ":key" );
-            assertEquals( "myregion" + " data " + i, value );
-        }
-
-        // remove partial
-        cache.remove( root + ":" );
-
-        for ( int i = 0; i < items; i++ )
-        {
-            assertNull( "Should have been removed by partial loop.", cache.get( root + ":" + i + ":key" ) );
-        }
-
-        assertNotNull( "Other item should be in the cache.", cache.get( "test" ) );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/memory/lru/LRUMemoryCacheConcurrentUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/memory/lru/LRUMemoryCacheConcurrentUnitTest.java
deleted file mode 100644
index 031691d..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/memory/lru/LRUMemoryCacheConcurrentUnitTest.java
+++ /dev/null
@@ -1,171 +0,0 @@
-package org.apache.commons.jcs.engine.memory.lru;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.extensions.ActiveTestSuite;
-import junit.framework.Test;
-import junit.framework.TestCase;
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.control.CompositeCache;
-import org.apache.commons.jcs.engine.control.CompositeCacheManager;
-
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Test which exercises the LRUMemory cache. This one uses three different
- * regions for three threads.
- */
-public class LRUMemoryCacheConcurrentUnitTest
-    extends TestCase
-{
-    /**
-     * Number of items to cache, twice the configured maxObjects for the memory
-     * cache regions.
-     */
-    private static int items = 200;
-
-    /**
-     * Constructor for the TestDiskCache object.
-     * <p>
-     * @param testName
-     */
-    public LRUMemoryCacheConcurrentUnitTest( String testName )
-    {
-        super( testName );
-    }
-
-    /**
-     * Main method passes this test to the text test runner.
-     * <p>
-     * @param args
-     */
-    public static void main( String args[] )
-    {
-        String[] testCaseName = { LRUMemoryCacheConcurrentUnitTest.class.getName() };
-        junit.textui.TestRunner.main( testCaseName );
-    }
-
-    /**
-     * A unit test suite for JUnit
-     * <p>
-     * @return The test suite
-     */
-    public static Test suite()
-    {
-        ActiveTestSuite suite = new ActiveTestSuite();
-
-        suite.addTest( new LRUMemoryCacheConcurrentUnitTest( "testLRUMemoryCache" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runTestForRegion( "testRegion1" );
-            }
-        } );
-
-        return suite;
-    }
-
-    /**
-     * Test setup
-     */
-    @Override
-    public void setUp()
-    {
-        //JCS.setConfigFilename( "/TestDiskCache.ccf" );
-    }
-
-    /**
-     * Adds items to cache, gets them, and removes them. The item count is more
-     * than the size of the memory cache, so items should be dumped.
-     * <p>
-     * @param region
-     *            Name of the region to access
-     * @throws Exception
-     *                If an error occurs
-     */
-    public void runTestForRegion( String region )
-        throws Exception
-    {
-        CompositeCacheManager cacheMgr = CompositeCacheManager.getUnconfiguredInstance();
-        cacheMgr.configure( "/TestDiskCache.ccf" );
-        CompositeCache<String, String> cache = cacheMgr.getCache( region );
-
-        LRUMemoryCache<String, String> lru = new LRUMemoryCache<>();
-        lru.initialize( cache );
-
-        // Add items to cache
-
-        for ( int i = 0; i < items; i++ )
-        {
-            ICacheElement<String, String> ice = new CacheElement<>( cache.getCacheName(), i + ":key", region + " data " + i );
-            ice.setElementAttributes( cache.getElementAttributes() );
-            lru.update( ice );
-        }
-
-        // Test that initial items have been purged
-        for ( int i = 0; i < 100; i++ )
-        {
-            assertNull( "Should not have " + i + ":key", lru.get( i + ":key" ) );
-        }
-
-        // Test that last items are in cache
-        for ( int i = 100; i < items; i++ )
-        {
-            String value = lru.get( i + ":key" ).getVal();
-            assertEquals( region + " data " + i, value );
-        }
-
-        // Test that getMultiple returns all the items remaining in cache and none of the missing ones
-        Set<String> keys = new HashSet<>();
-        for ( int i = 0; i < items; i++ )
-        {
-            keys.add( i + ":key" );
-        }
-
-        Map<String, ICacheElement<String, String>> elements = lru.getMultiple( keys );
-        for ( int i = 0; i < 100; i++ )
-        {
-            assertNull( "Should not have " + i + ":key", elements.get( i + ":key" ) );
-        }
-        for ( int i = 100; i < items; i++ )
-        {
-            ICacheElement<String, String> element = elements.get( i + ":key" );
-            assertNotNull( "element " + i + ":key is missing", element );
-            assertEquals( "value " + i + ":key", region + " data " + i, element.getVal() );
-        }
-
-        // Remove all the items
-        for ( int i = 0; i < items; i++ )
-        {
-            lru.remove( i + ":key" );
-        }
-
-        // Verify removal
-        for ( int i = 0; i < items; i++ )
-        {
-            assertNull( "Removed key should be null: " + i + ":key", lru.get( i + ":key" ) );
-        }
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/memory/mru/LRUvsMRUPerformanceTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/memory/mru/LRUvsMRUPerformanceTest.java
deleted file mode 100644
index 8d3343b..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/memory/mru/LRUvsMRUPerformanceTest.java
+++ /dev/null
@@ -1,183 +0,0 @@
-package org.apache.commons.jcs.engine.memory.mru;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache;
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-/**
- * Tests the performance difference between the LRU and the MRU. There should be very little.
- */
-public class LRUvsMRUPerformanceTest
-    extends TestCase
-{
-    /** ration we want */
-    float ratioPut = 0;
-
-    /** ration we want */
-    float ratioGet = 0;
-
-    /** ration we want */
-    float target = 1.20f;
-
-    /** times to run */
-    int loops = 20;
-
-    /** item per run */
-    int tries = 10000;
-
-    /**
-     * A unit test for JUnit
-     * @throws Exception Description of the Exception
-     */
-    public void testSimpleLoad()
-        throws Exception
-    {
-        Log log1 = LogManager.getLog( LRUMemoryCache.class );
-        if ( log1.isDebugEnabled() )
-        {
-            System.out.println( "The log level must be at info or above for the a performance test." );
-            return;
-        }
-        Log log2 = LogManager.getLog( MRUMemoryCache.class );
-        if ( log2.isDebugEnabled() )
-        {
-            System.out.println( "The log level must be at info or above for the a performance test." );
-            return;
-        }
-        doWork();
-
-        // these were when the mru was implemented with the jdk linked list
-        //assertTrue( "Ratio is unacceptible.", this.ratioPut < target );
-        ///assertTrue( "Ratio is unacceptible.", this.ratioGet < target );
-    }
-
-    /**
-     * Runs the test
-     */
-    public void doWork()
-    {
-
-        long start = 0;
-        long end = 0;
-        long time = 0;
-        float tPer = 0;
-
-        long putTotalLRU = 0;
-        long getTotalLRU = 0;
-        long putTotalMRU = 0;
-        long getTotalMRU = 0;
-
-        try
-        {
-
-            JCS.setConfigFilename( "/TestMRUCache.ccf" );
-            CacheAccess<String, String> cache = JCS.getInstance( "lruDefined" );
-            CacheAccess<String, String> mru = JCS.getInstance( "mruDefined" );
-
-            System.out.println( "LRU = " + cache );
-
-            for ( int j = 0; j < loops; j++ )
-            {
-
-                System.out.println( "Beginning loop " + j );
-
-                String name = "LRU      ";
-                start = System.currentTimeMillis();
-                for ( int i = 0; i < tries; i++ )
-                {
-                    cache.put( "key:" + i, "data" + i );
-                }
-                end = System.currentTimeMillis();
-                time = end - start;
-                putTotalLRU += time;
-                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
-                System.out.println( name + " put time for " + tries + " = " + time + "; millis per = " + tPer );
-
-                start = System.currentTimeMillis();
-                for ( int i = 0; i < tries; i++ )
-                {
-                    cache.get( "key:" + i );
-                }
-                end = System.currentTimeMillis();
-                time = end - start;
-                getTotalLRU += time;
-                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
-                System.out.println( name + " get time for " + tries + " = " + time + "; millis per = " + tPer );
-
-                // /////////////////////////////////////////////////////////////
-                name = "MRU";
-                start = System.currentTimeMillis();
-                for ( int i = 0; i < tries; i++ )
-                {
-                    mru.put( "key:" + i, "data" + i );
-                }
-                end = System.currentTimeMillis();
-                time = end - start;
-                putTotalMRU += time;
-                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
-                System.out.println( name + " put time for " + tries + " = " + time + "; millis per = " + tPer );
-
-                start = System.currentTimeMillis();
-                for ( int i = 0; i < tries; i++ )
-                {
-                    mru.get( "key:" + i );
-                }
-                end = System.currentTimeMillis();
-                time = end - start;
-                getTotalMRU += time;
-                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
-                System.out.println( name + " get time for " + tries + " = " + time + "; millis per = " + tPer );
-
-                System.out.println( "\n" );
-            }
-
-        }
-        catch ( Exception e )
-        {
-            e.printStackTrace( System.out );
-            System.out.println( e );
-        }
-
-        long putAvJCS = putTotalLRU / loops;
-        long getAvJCS = getTotalLRU / loops;
-        long putAvHashtable = putTotalMRU / loops;
-        long getAvHashtable = getTotalMRU / loops;
-
-        System.out.println( "Finished " + loops + " loops of " + tries + " gets and puts" );
-
-        System.out.println( "\n" );
-        System.out.println( "Put average for JCS       = " + putAvJCS );
-        System.out.println( "Put average for MRU = " + putAvHashtable );
-        ratioPut = Float.intBitsToFloat( (int) putAvJCS ) / Float.intBitsToFloat( (int) putAvHashtable );
-        System.out.println( "JCS puts took " + ratioPut + " times the Hashtable, the goal is <" + target + "x" );
-
-        System.out.println( "\n" );
-        System.out.println( "Get average for JCS       = " + getAvJCS );
-        System.out.println( "Get average for MRU = " + getAvHashtable );
-        ratioGet = Float.intBitsToFloat( (int) getAvJCS ) / Float.intBitsToFloat( (int) getAvHashtable );
-        System.out.println( "JCS gets took " + ratioGet + " times the Hashtable, the goal is <" + target + "x" );
-    }
-
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/memory/mru/MRUMemoryCacheUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/memory/mru/MRUMemoryCacheUnitTest.java
deleted file mode 100644
index 7b427d2..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/memory/mru/MRUMemoryCacheUnitTest.java
+++ /dev/null
@@ -1,312 +0,0 @@
-package org.apache.commons.jcs.engine.memory.mru;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.access.exception.CacheException;
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.control.CompositeCache;
-import org.apache.commons.jcs.engine.control.CompositeCacheManager;
-
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Tests for the test MRU implementation.
- * <p>
- * @author Aaron Smuts
- */
-public class MRUMemoryCacheUnitTest
-    extends TestCase
-{
-    /** Test setup */
-    @Override
-    public void setUp()
-    {
-        JCS.setConfigFilename( "/TestMRUCache.ccf" );
-    }
-
-    /**
-     * Verify that the mru gets used by a non-defined region when it is set as the default in the
-     * default region.
-     * <p>
-     * @throws CacheException
-     */
-    public void testLoadFromCCF()
-        throws CacheException
-    {
-        CacheAccess<String, String> cache = JCS.getInstance( "testPutGet" );
-        String memoryCacheName = cache.getCacheAttributes().getMemoryCacheName();
-        assertTrue( "Cache name should have MRU in it.", memoryCacheName.indexOf( "MRUMemoryCache" ) != -1 );
-    }
-
-    /**
-     * put twice as many as the max.  verify that the second half is in the cache.
-     * <p>
-     * @throws CacheException
-     */
-    public void testPutGetThroughHub()
-        throws CacheException
-    {
-        CacheAccess<String, String> cache = JCS.getInstance( "testPutGetThroughHub" );
-
-        int max = cache.getCacheAttributes().getMaxObjects();
-        int items = max * 2;
-
-        for ( int i = 0; i < items; i++ )
-        {
-            cache.put( i + ":key", "myregion" + " data " + i );
-        }
-
-        // Test that first items are not in the cache
-        for ( int i = max -1; i >= 0; i-- )
-        {
-            String value = cache.get( i + ":key" );
-            assertNull( "Should not have value for key [" + i + ":key" + "] in the cache." + cache.getStats(), value );
-        }
-
-        // Test that last items are in cache
-        // skip 2 for the buffer.
-        for ( int i = max + 2; i < items; i++ )
-        {
-            String value = cache.get( i + ":key" );
-            assertEquals( "myregion" + " data " + i, value );
-        }
-
-        // Test that getMultiple returns all the items remaining in cache and none of the missing ones
-        Set<String> keys = new HashSet<>();
-        for ( int i = 0; i < items; i++ )
-        {
-            keys.add( i + ":key" );
-        }
-
-        Map<String, ICacheElement<String, String>> elements = cache.getCacheElements( keys );
-        for ( int i = max-1; i >= 0; i-- )
-        {
-            assertNull( "Should not have value for key [" + i + ":key" + "] in the cache." + cache.getStats(), elements.get( i + ":key" ) );
-        }
-        for ( int i = max + 2; i < items; i++ )
-        {
-            ICacheElement<String, String> element = elements.get( i + ":key" );
-            assertNotNull( "element " + i + ":key is missing", element );
-            assertEquals( "value " + i + ":key", "myregion" + " data " + i, element.getVal() );
-        }
-    }
-
-    /**
-     * Put twice as many as the max, twice. verify that the second half is in the cache.
-     * <p>
-     * @throws CacheException
-     */
-    public void testPutGetThroughHubTwice()
-        throws CacheException
-    {
-        CacheAccess<String, String> cache = JCS.getInstance( "testPutGetThroughHub" );
-
-        int max = cache.getCacheAttributes().getMaxObjects();
-        int items = max * 2;
-
-        for ( int i = 0; i < items; i++ )
-        {
-            cache.put( i + ":key", "myregion" + " data " + i );
-        }
-
-        for ( int i = 0; i < items; i++ )
-        {
-            cache.put( i + ":key", "myregion" + " data " + i );
-        }
-
-        // Test that first items are not in the cache
-        for ( int i = max-1; i >= 0; i-- )
-        {
-            String value = cache.get( i + ":key" );
-            assertNull( "Should not have value for key [" + i + ":key" + "] in the cache.", value );
-        }
-
-        // Test that last items are in cache
-        // skip 2 for the buffer.
-        for ( int i = max + 2; i < items; i++ )
-        {
-            String value = cache.get( i + ":key" );
-            assertEquals( "myregion" + " data " + i, value );
-        }
-
-    }
-
-    /**
-     * put the max and remove each. verify that they are all null.
-     * <p>
-     * @throws CacheException
-     */
-    public void testPutRemoveThroughHub()
-        throws CacheException
-    {
-        CacheAccess<String, String> cache = JCS.getInstance( "testPutGetThroughHub" );
-
-        int max = cache.getCacheAttributes().getMaxObjects();
-        int items = max * 2;
-
-        for ( int i = 0; i < items; i++ )
-        {
-            cache.put( i + ":key", "myregion" + " data " + i );
-        }
-
-        for ( int i = 0; i < items; i++ )
-        {
-            cache.remove( i + ":key" );
-        }
-
-        // Test that first items are not in the cache
-        for ( int i = max; i >= 0; i-- )
-        {
-            String value = cache.get( i + ":key" );
-            assertNull( "Should not have value for key [" + i + ":key" + "] in the cache.", value );
-        }
-    }
-
-    /**
-     * put the max and clear. verify that no elements remain.
-     * <p>
-     * @throws CacheException
-     */
-    public void testClearThroughHub()
-        throws CacheException
-    {
-        CacheAccess<String, String> cache = JCS.getInstance( "testPutGetThroughHub" );
-
-        int max = cache.getCacheAttributes().getMaxObjects();
-        int items = max * 2;
-
-        for ( int i = 0; i < items; i++ )
-        {
-            cache.put( i + ":key", "myregion" + " data " + i );
-        }
-
-        cache.clear();
-
-        // Test that first items are not in the cache
-        for ( int i = max; i >= 0; i-- )
-        {
-            String value = cache.get( i + ":key" );
-            assertNull( "Should not have value for key [" + i + ":key" + "] in the cache.", value );
-        }
-    }
-
-    /**
-     * Get stats.
-     * <p>
-     * @throws CacheException
-     */
-    public void testGetStatsThroughHub()
-        throws CacheException
-    {
-        CacheAccess<String, String> cache = JCS.getInstance( "testGetStatsThroughHub" );
-
-        int max = cache.getCacheAttributes().getMaxObjects();
-        int items = max * 2;
-
-        for ( int i = 0; i < items; i++ )
-        {
-            cache.put( i + ":key", "myregion" + " data " + i );
-        }
-
-        String stats = cache.getStats();
-
-//        System.out.println( stats );
-
-        // TODO improve stats check
-        assertTrue( "Should have 200 puts", stats.indexOf( "2000" ) != -1 );
-    }
-
-    /**
-     * Put half the max and clear. get the key array and verify that it has the correct number of
-     * items.
-     * <p>
-     * @throws Exception
-     */
-    public void testGetKeyArray()
-        throws Exception
-    {
-        CompositeCacheManager cacheMgr = CompositeCacheManager.getUnconfiguredInstance();
-        cacheMgr.configure( "/TestMRUCache.ccf" );
-        CompositeCache<String, String> cache = cacheMgr.getCache( "testGetKeyArray" );
-
-        MRUMemoryCache<String, String> mru = new MRUMemoryCache<>();
-        mru.initialize( cache );
-
-        int max = cache.getCacheAttributes().getMaxObjects();
-        int items = max / 2;
-
-        for ( int i = 0; i < items; i++ )
-        {
-            ICacheElement<String, String> ice = new CacheElement<>( cache.getCacheName(), i + ":key", cache.getCacheName() + " data " + i );
-            ice.setElementAttributes( cache.getElementAttributes() );
-            mru.update( ice );
-        }
-
-        Set<String> keys = mru.getKeySet();
-
-        assertEquals( "Wrong number of keys.", items, keys.size() );
-    }
-
-    /**
-     * Add a few keys with the delimiter. Remove them.
-     * <p>
-     * @throws CacheException
-     */
-    public void testRemovePartialThroughHub()
-        throws CacheException
-    {
-        CacheAccess<String, String> cache = JCS.getInstance( "testGetStatsThroughHub" );
-
-        int max = cache.getCacheAttributes().getMaxObjects();
-        int items = max / 2;
-
-        cache.put( "test", "data" );
-
-        String root = "myroot";
-
-        for ( int i = 0; i < items; i++ )
-        {
-            cache.put( root + ":" + i + ":key", "myregion" + " data " + i );
-        }
-
-        // Test that last items are in cache
-        for ( int i = 0; i < items; i++ )
-        {
-            String value = cache.get( root + ":" + i + ":key" );
-            assertEquals( "myregion" + " data " + i, value );
-        }
-
-        // remove partial
-        cache.remove( root + ":" );
-
-        for ( int i = 0; i < items; i++ )
-        {
-            assertNull( "Should have been removed by partial loop.", cache.get( root + ":" + i + ":key" ) );
-        }
-
-        assertNotNull( "Other item should be in the cache.", cache.get( "test" ) );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/memory/shrinking/ShrinkerThreadUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/memory/shrinking/ShrinkerThreadUnitTest.java
deleted file mode 100644
index 97511fe..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/memory/shrinking/ShrinkerThreadUnitTest.java
+++ /dev/null
@@ -1,336 +0,0 @@
-package org.apache.commons.jcs.engine.memory.shrinking;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.CompositeCacheAttributes;
-import org.apache.commons.jcs.engine.ElementAttributes;
-import org.apache.commons.jcs.engine.ElementAttributesUtils;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.control.CompositeCache;
-import org.apache.commons.jcs.engine.control.event.ElementEventHandlerMockImpl;
-import org.apache.commons.jcs.engine.control.event.behavior.ElementEventType;
-import org.apache.commons.jcs.engine.memory.MockMemoryCache;
-
-import java.io.IOException;
-
-/**
- * This tests the functionality of the shrinker thread.
- * <p>
- * @author Aaron Smuts
- */
-public class ShrinkerThreadUnitTest
-    extends TestCase
-{
-    /** verify the check for removal
-     * <p>
-     * @throws IOException */
-    public void testCheckForRemoval_Expired() throws IOException
-    {
-        // SETUP
-        CompositeCacheAttributes cacheAttr = new CompositeCacheAttributes();
-        cacheAttr.setCacheName("testRegion");
-        cacheAttr.setMaxMemoryIdleTimeSeconds( 10 );
-        cacheAttr.setMaxSpoolPerRun( 10 );
-
-        CompositeCache<String, String> cache = new CompositeCache<>(cacheAttr, new ElementAttributes());
-
-        String key = "key";
-        String value = "value";
-
-        ICacheElement<String, String> element = new CacheElement<>( "testRegion", key, value );
-        ElementAttributes elementAttr = new ElementAttributes();
-        elementAttr.setIsEternal( false );
-        element.setElementAttributes( elementAttr );
-        element.getElementAttributes().setMaxLife(1);
-
-        long now = System.currentTimeMillis();
-        // add two seconds
-        now += 2000;
-
-        // DO WORK
-        boolean result = cache.isExpired( element, now,
-                ElementEventType.EXCEEDED_MAXLIFE_BACKGROUND,
-                ElementEventType.EXCEEDED_IDLETIME_BACKGROUND );
-
-        // VERIFY
-        assertTrue( "Item should have expired.", result );
-    }
-
-    /** verify the check for removal
-     * <p>
-     * @throws IOException */
-    public void testCheckForRemoval_NotExpired() throws IOException
-    {
-        // SETUP
-        CompositeCacheAttributes cacheAttr = new CompositeCacheAttributes();
-        cacheAttr.setCacheName("testRegion");
-        cacheAttr.setMaxMemoryIdleTimeSeconds( 10 );
-        cacheAttr.setMaxSpoolPerRun( 10 );
-
-        CompositeCache<String, String> cache = new CompositeCache<>(cacheAttr, new ElementAttributes());
-
-        String key = "key";
-        String value = "value";
-
-        ICacheElement<String, String> element = new CacheElement<>( "testRegion", key, value );
-        ElementAttributes elementAttr = new ElementAttributes();
-        elementAttr.setIsEternal( false );
-        element.setElementAttributes( elementAttr );
-        element.getElementAttributes().setMaxLife(1);
-
-        long now = System.currentTimeMillis();
-        // subtract two seconds
-        now -= 2000;
-
-        // DO WORK
-        boolean result = cache.isExpired( element, now,
-                ElementEventType.EXCEEDED_MAXLIFE_BACKGROUND,
-                ElementEventType.EXCEEDED_IDLETIME_BACKGROUND );
-
-        // VERIFY
-        assertFalse( "Item should not have expired.", result );
-    }
-
-    /** verify the check for removal
-     * <p>
-     * @throws IOException */
-    public void testCheckForRemoval_IdleTooLong() throws IOException
-    {
-        // SETUP
-        CompositeCacheAttributes cacheAttr = new CompositeCacheAttributes();
-        cacheAttr.setCacheName("testRegion");
-        cacheAttr.setMaxMemoryIdleTimeSeconds( 10 );
-        cacheAttr.setMaxSpoolPerRun( 10 );
-
-        CompositeCache<String, String> cache = new CompositeCache<>(cacheAttr, new ElementAttributes());
-
-        String key = "key";
-        String value = "value";
-
-        ICacheElement<String, String> element = new CacheElement<>( "testRegion", key, value );
-        ElementAttributes elementAttr = new ElementAttributes();
-        elementAttr.setIsEternal( false );
-        element.setElementAttributes( elementAttr );
-        element.getElementAttributes().setMaxLife(100);
-        element.getElementAttributes().setIdleTime( 1 );
-
-        long now = System.currentTimeMillis();
-        // add two seconds
-        now += 2000;
-
-        // DO WORK
-        boolean result = cache.isExpired( element, now,
-                ElementEventType.EXCEEDED_MAXLIFE_BACKGROUND,
-                ElementEventType.EXCEEDED_IDLETIME_BACKGROUND );
-
-        // VERIFY
-        assertTrue( "Item should have expired.", result );
-    }
-
-    /** verify the check for removal
-     * <p>
-     * @throws IOException */
-    public void testCheckForRemoval_NotIdleTooLong() throws IOException
-    {
-        // SETUP
-        CompositeCacheAttributes cacheAttr = new CompositeCacheAttributes();
-        cacheAttr.setCacheName("testRegion");
-        cacheAttr.setMaxMemoryIdleTimeSeconds( 10 );
-        cacheAttr.setMaxSpoolPerRun( 10 );
-
-        CompositeCache<String, String> cache = new CompositeCache<>(cacheAttr, new ElementAttributes());
-
-        String key = "key";
-        String value = "value";
-
-        ICacheElement<String, String> element = new CacheElement<>( "testRegion", key, value );
-        ElementAttributes elementAttr = new ElementAttributes();
-        elementAttr.setIsEternal( false );
-        element.setElementAttributes( elementAttr );
-        element.getElementAttributes().setMaxLife(100);
-        element.getElementAttributes().setIdleTime( 1 );
-
-        long now = System.currentTimeMillis();
-        // subtract two seconds
-        now -= 2000;
-
-        // DO WORK
-        boolean result = cache.isExpired( element, now,
-                ElementEventType.EXCEEDED_MAXLIFE_BACKGROUND,
-                ElementEventType.EXCEEDED_IDLETIME_BACKGROUND );
-
-        // VERIFY
-        assertFalse( "Item should not have expired.", result );
-    }
-
-    /**
-     * Setup cache attributes in mock. Create the shrinker with the mock. Add some elements into the
-     * mock memory cache see that they get spooled.
-     * <p>
-     * @throws Exception
-     */
-    public void testSimpleShrink()
-        throws Exception
-    {
-        // SETUP
-        CompositeCacheAttributes cacheAttr = new CompositeCacheAttributes();
-        cacheAttr.setCacheName("testRegion");
-        cacheAttr.setMemoryCacheName("org.apache.commons.jcs.engine.memory.MockMemoryCache");
-        cacheAttr.setMaxMemoryIdleTimeSeconds( 1 );
-        cacheAttr.setMaxSpoolPerRun( 10 );
-
-        CompositeCache<String, String> cache = new CompositeCache<>(cacheAttr, new ElementAttributes());
-        MockMemoryCache<String, String> memory = (MockMemoryCache<String, String>)cache.getMemoryCache();
-
-        String key = "key";
-        String value = "value";
-
-        ICacheElement<String, String> element = new CacheElement<>( "testRegion", key, value );
-
-        ElementAttributes elementAttr = new ElementAttributes();
-        elementAttr.setIsEternal( false );
-        element.setElementAttributes( elementAttr );
-        element.getElementAttributes().setMaxLife(1);
-        memory.update( element );
-
-        ICacheElement<String, String> returnedElement1 = memory.get( key );
-        assertNotNull( "We should have received an element", returnedElement1 );
-
-        // set this to 2 seconds ago.
-        ElementAttributesUtils.setLastAccessTime( elementAttr,  System.currentTimeMillis() - 2000 );
-
-        // DO WORK
-        ShrinkerThread<String, String> shrinker = new ShrinkerThread<>( cache );
-        shrinker.run();
-
-        Thread.sleep( 500 );
-
-        // VERIFY
-        ICacheElement<String, String> returnedElement2 = memory.get( key );
-        assertTrue( "Waterfall should have been called.", memory.waterfallCallCount > 0 );
-        assertNull( "We not should have received an element.  It should have been spooled.", returnedElement2 );
-    }
-
-    /**
-     * Add 10 to the memory cache. Set the spool per run limit to 3.
-     * <p>
-     * @throws Exception
-     */
-    public void testSimpleShrinkMultiple()
-        throws Exception
-    {
-        // SETUP
-        CompositeCacheAttributes cacheAttr = new CompositeCacheAttributes();
-        cacheAttr.setCacheName("testRegion");
-        cacheAttr.setMemoryCacheName("org.apache.commons.jcs.engine.memory.MockMemoryCache");
-        cacheAttr.setMaxMemoryIdleTimeSeconds( 1 );
-        cacheAttr.setMaxSpoolPerRun( 3 );
-
-        CompositeCache<String, String> cache = new CompositeCache<>(cacheAttr, new ElementAttributes());
-        MockMemoryCache<String, String> memory = (MockMemoryCache<String, String>)cache.getMemoryCache();
-
-        for ( int i = 0; i < 10; i++ )
-        {
-            String key = "key" + i;
-            String value = "value";
-
-            ICacheElement<String, String> element = new CacheElement<>( "testRegion", key, value );
-
-            ElementAttributes elementAttr = new ElementAttributes();
-            elementAttr.setIsEternal( false );
-            element.setElementAttributes( elementAttr );
-            element.getElementAttributes().setMaxLife(1);
-            memory.update( element );
-
-            ICacheElement<String, String> returnedElement1 = memory.get( key );
-            assertNotNull( "We should have received an element", returnedElement1 );
-
-            // set this to 2 seconds ago.
-            ElementAttributesUtils.setLastAccessTime( elementAttr,  System.currentTimeMillis() - 2000 );
-        }
-
-        // DO WORK
-        ShrinkerThread<String, String> shrinker = new ShrinkerThread<>( cache );
-        shrinker.run();
-
-        // VERIFY
-        Thread.sleep( 500 );
-        assertEquals( "Waterfall called the wrong number of times.", 3, memory.waterfallCallCount );
-        assertEquals( "Wrong number of elements remain.", 7, memory.getSize() );
-    }
-
-    /**
-     * Add a mock event handler to the items. Verify that it gets called.
-     * <p>
-     * This is only testing the spooled background event
-     * <p>
-     * @throws Exception
-     */
-    public void testSimpleShrinkMultipleWithEventHandler()
-        throws Exception
-    {
-        // SETUP
-        CompositeCacheAttributes cacheAttr = new CompositeCacheAttributes();
-        cacheAttr.setCacheName("testRegion");
-        cacheAttr.setMemoryCacheName("org.apache.commons.jcs.engine.memory.MockMemoryCache");
-        cacheAttr.setMaxMemoryIdleTimeSeconds( 1 );
-        cacheAttr.setMaxSpoolPerRun( 3 );
-
-        CompositeCache<String, String> cache = new CompositeCache<>(cacheAttr, new ElementAttributes());
-        MockMemoryCache<String, String> memory = (MockMemoryCache<String, String>)cache.getMemoryCache();
-
-        ElementEventHandlerMockImpl handler = new ElementEventHandlerMockImpl();
-
-        for ( int i = 0; i < 10; i++ )
-        {
-            String key = "key" + i;
-            String value = "value";
-
-            ICacheElement<String, String> element = new CacheElement<>( "testRegion", key, value );
-
-            ElementAttributes elementAttr = new ElementAttributes();
-            elementAttr.addElementEventHandler( handler );
-            elementAttr.setIsEternal( false );
-            element.setElementAttributes( elementAttr );
-            element.getElementAttributes().setMaxLife(1);
-            memory.update( element );
-
-            ICacheElement<String, String> returnedElement1 = memory.get( key );
-            assertNotNull( "We should have received an element", returnedElement1 );
-
-            // set this to 2 seconds ago.
-            ElementAttributesUtils.setLastAccessTime( elementAttr,  System.currentTimeMillis() - 2000 );
-        }
-
-        // DO WORK
-        ShrinkerThread<String, String> shrinker = new ShrinkerThread<>( cache );
-        shrinker.run();
-
-        // VERIFY
-        Thread.sleep( 500 );
-        assertEquals( "Waterfall called the wrong number of times.", 3, memory.waterfallCallCount );
-        // the shrinker delegates the the composite cache on the memory cache to put the
-        // event on the queue.  This make it hard to test.  TODO we need to change this to make it easier to verify.
-        //assertEquals( "Event handler ExceededIdleTimeBackground called the wrong number of times.", 3, handler.getExceededIdleTimeBackgroundCount() );
-        assertEquals( "Wrong number of elements remain.", 7, memory.getSize() );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/memory/soft/SoftReferenceMemoryCacheUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/memory/soft/SoftReferenceMemoryCacheUnitTest.java
deleted file mode 100644
index 3a96d68..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/engine/memory/soft/SoftReferenceMemoryCacheUnitTest.java
+++ /dev/null
@@ -1,247 +0,0 @@
-package org.apache.commons.jcs.engine.memory.soft;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-import junit.framework.TestCase;
-
-import org.apache.commons.jcs.JCS;
-import org.apache.commons.jcs.access.CacheAccess;
-import org.apache.commons.jcs.access.exception.CacheException;
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.control.CompositeCache;
-import org.apache.commons.jcs.engine.control.CompositeCacheManager;
-
-/**
- * Tests for the test Soft reference implementation.
- * <p>
- * @author Aaron Smuts
- */
-public class SoftReferenceMemoryCacheUnitTest
-    extends TestCase
-{
-    /** Test setup */
-    @Override
-    public void setUp()
-    {
-        JCS.setConfigFilename( "/TestSoftReferenceCache.ccf" );
-    }
-
-    /**
-     * @see junit.framework.TestCase#tearDown()
-     */
-    @Override
-    protected void tearDown() throws Exception
-    {
-        CompositeCacheManager.getInstance().shutDown();
-    }
-
-    /**
-     * Verify that the cache gets used by a non-defined region when it is set as the default in the
-     * default region.
-     * <p>
-     * @throws CacheException
-     */
-    public void testLoadFromCCF()
-        throws CacheException
-    {
-        CacheAccess<String, String> cache = JCS.getInstance( "testPutGet" );
-        String memoryCacheName = cache.getCacheAttributes().getMemoryCacheName();
-        assertTrue( "Cache name should have SoftReference in it.",
-                memoryCacheName.indexOf( "SoftReferenceMemoryCache" ) != -1 );
-    }
-
-    /**
-     * put twice as many as the max.  verify that all are in the cache.
-     * <p>
-     * @throws CacheException
-     */
-    public void testPutGetThroughHub()
-        throws CacheException
-    {
-        CacheAccess<String, String> cache = JCS.getInstance( "testPutGetThroughHub" );
-
-        int max = cache.getCacheAttributes().getMaxObjects();
-        int items = max * 2;
-
-        for ( int i = 0; i < items; i++ )
-        {
-            cache.put( i + ":key", "myregion" + " data " + i );
-        }
-
-        // Test that all items are in cache
-        for ( int i = 0; i < items; i++ )
-        {
-            String value = cache.get( i + ":key" );
-            assertEquals( "myregion" + " data " + i, value );
-        }
-
-        // Test that getMultiple returns all the items remaining in cache and none of the missing ones
-        Set<String> keys = new HashSet<>();
-        for ( int i = 0; i < items; i++ )
-        {
-            keys.add( i + ":key" );
-        }
-
-        Map<String, ICacheElement<String, String>> elements = cache.getCacheElements( keys );
-        for ( int i = 0; i < items; i++ )
-        {
-            ICacheElement<String, String> element = elements.get( i + ":key" );
-            assertNotNull( "element " + i + ":key is missing", element );
-            assertEquals( "value " + i + ":key", "myregion" + " data " + i, element.getVal() );
-        }
-
-        // System.out.println(cache.getStats());
-    }
-
-    /**
-     * put the max and remove each. verify that they are all null.
-     * <p>
-     * @throws CacheException
-     */
-    public void testPutRemoveThroughHub()
-        throws CacheException
-    {
-        CacheAccess<String, String> cache = JCS.getInstance( "testPutGetThroughHub" );
-
-        int max = cache.getCacheAttributes().getMaxObjects();
-        int items = max * 2;
-
-        for ( int i = 0; i < items; i++ )
-        {
-            cache.put( i + ":key", "myregion" + " data " + i );
-        }
-
-        for ( int i = 0; i < items; i++ )
-        {
-            cache.remove( i + ":key" );
-        }
-
-        // Test that first items are not in the cache
-        for ( int i = max; i >= 0; i-- )
-        {
-            String value = cache.get( i + ":key" );
-            assertNull( "Should not have value for key [" + i + ":key" + "] in the cache.", value );
-        }
-    }
-
-    /**
-     * put the max and clear. verify that no elements remain.
-     * <p>
-     * @throws CacheException
-     */
-    public void testClearThroughHub()
-        throws CacheException
-    {
-        CacheAccess<String, String> cache = JCS.getInstance( "testPutGetThroughHub" );
-
-        int max = cache.getCacheAttributes().getMaxObjects();
-        int items = max * 2;
-
-        for ( int i = 0; i < items; i++ )
-        {
-            cache.put( i + ":key", "myregion" + " data " + i );
-        }
-
-        cache.clear();
-
-        // Test that first items are not in the cache
-        for ( int i = max; i >= 0; i-- )
-        {
-            String value = cache.get( i + ":key" );
-            assertNull( "Should not have value for key [" + i + ":key" + "] in the cache.", value );
-        }
-    }
-
-    /**
-     * Put half the max and clear. get the key array and verify that it has the correct number of
-     * items.
-     * <p>
-     * @throws Exception
-     */
-    public void testGetKeyArray()
-        throws Exception
-    {
-        CompositeCacheManager cacheMgr = CompositeCacheManager.getUnconfiguredInstance();
-        cacheMgr.configure( "/TestSoftReferenceCache.ccf" );
-        CompositeCache<String, String> cache = cacheMgr.getCache( "testGetKeyArray" );
-
-        SoftReferenceMemoryCache<String, String> srmc = new SoftReferenceMemoryCache<>();
-        srmc.initialize( cache );
-
-        int max = cache.getCacheAttributes().getMaxObjects();
-        int items = max / 2;
-
-        for ( int i = 0; i < items; i++ )
-        {
-            ICacheElement<String, String> ice = new CacheElement<>( cache.getCacheName(), i + ":key", cache.getCacheName() + " data " + i );
-            ice.setElementAttributes( cache.getElementAttributes() );
-            srmc.update( ice );
-        }
-
-        Set<String> keys = srmc.getKeySet();
-
-        assertEquals( "Wrong number of keys.", items, keys.size() );
-    }
-
-    /**
-     * Add a few keys with the delimiter. Remove them.
-     * <p>
-     * @throws CacheException
-     */
-    public void testRemovePartialThroughHub()
-        throws CacheException
-    {
-        CacheAccess<String, String> cache = JCS.getInstance( "testGetStatsThroughHub" );
-
-        int max = cache.getCacheAttributes().getMaxObjects();
-        int items = max / 2;
-
-        cache.put( "test", "data" );
-
-        String root = "myroot";
-
-        for ( int i = 0; i < items; i++ )
-        {
-            cache.put( root + ":" + i + ":key", "myregion" + " data " + i );
-        }
-
-        // Test that last items are in cache
-        for ( int i = 0; i < items; i++ )
-        {
-            String value = cache.get( root + ":" + i + ":key" );
-            assertEquals( "myregion" + " data " + i, value );
-        }
-
-        // remove partial
-        cache.remove( root + ":" );
-
-        for ( int i = 0; i < items; i++ )
-        {
-            assertNull( "Should have been removed by partial loop.", cache.get( root + ":" + i + ":key" ) );
-        }
-
-        assertNotNull( "Other item should be in the cache.", cache.get( "test" ) );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/access/JCSWorkerUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/access/JCSWorkerUnitTest.java
deleted file mode 100644
index 7dec5d3..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/access/JCSWorkerUnitTest.java
+++ /dev/null
@@ -1,69 +0,0 @@
-package org.apache.commons.jcs.utils.access;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-
-/**
- * Test cases for the JCS worker.
- *
- * @author Aaron Smuts
- *
- */
-public class JCSWorkerUnitTest
-    extends TestCase
-{
-
-    /**
-     * Test basic worker functionality.  This is a serial not a concurrent test.
-     * <p>
-     * Just verify that the worker will go to the cache before asking the helper.
-     *
-     * @throws Exception
-     *
-     */
-    public void testSimpleGet()
-        throws Exception
-    {
-        JCSWorker<String, Long> cachingWorker = new JCSWorker<>( "example region" );
-
-        // This is the helper.
-        JCSWorkerHelper<Long> helper = new AbstractJCSWorkerHelper<Long>()
-        {
-            int timesCalled = 0;
-
-            @Override
-            public Long doWork()
-            {
-                return Long.valueOf( ++timesCalled );
-            }
-        };
-
-        String key = "abc";
-
-        Long result = cachingWorker.getResult( key, helper );
-        assertEquals( "Called the wrong number of times", Long.valueOf( 1 ), result );
-
-        // should get it from the cache.
-        Long result2 = cachingWorker.getResult( key, helper );
-        assertEquals( "Called the wrong number of times", Long.valueOf( 1 ), result2 );
-    }
-
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/config/PropertySetterUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/config/PropertySetterUnitTest.java
deleted file mode 100644
index bb363f7..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/config/PropertySetterUnitTest.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package org.apache.commons.jcs.utils.config;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import static org.junit.Assert.*;
-
-import java.io.File;
-
-import org.junit.Test;
-
-/**
- * Test property settings
- *
- * @author Thomas Vandahl
- *
- */
-public class PropertySetterUnitTest
-{
-    enum EnumTest { ONE, TWO, THREE };
-
-    @Test
-    public void testConvertArg()
-    {
-        PropertySetter ps = new PropertySetter(this);
-        Object s = ps.convertArg("test", String.class);
-        assertEquals("Should be a string", "test", s);
-
-        Object i = ps.convertArg("1", Integer.TYPE);
-        assertEquals("Should be an integer", Integer.valueOf(1), i);
-
-        Object l = ps.convertArg("1", Long.TYPE);
-        assertEquals("Should be a long", Long.valueOf(1), l);
-
-        Object b = ps.convertArg("true", Boolean.TYPE);
-        assertEquals("Should be a boolean", Boolean.TRUE, b);
-
-        Object e = ps.convertArg("TWO", EnumTest.class);
-        assertEquals("Should be an enum", EnumTest.TWO, e);
-
-        Object f = ps.convertArg("test.conf", File.class);
-        assertTrue("Should be a file", f instanceof File);
-    }
-
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/discovery/MockDiscoveryListener.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/discovery/MockDiscoveryListener.java
deleted file mode 100644
index 746639a..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/discovery/MockDiscoveryListener.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package org.apache.commons.jcs.utils.discovery;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.commons.jcs.utils.discovery.behavior.IDiscoveryListener;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/** Mock listener, for testing. */
-public class MockDiscoveryListener
-    implements IDiscoveryListener
-{
-    /** discovered services. */
-    public List<DiscoveredService> discoveredServices = new ArrayList<>();
-
-    /**
-     * Adds the entry to a list. I'm not using a set. I want to see if we get dupes.
-     * <p>
-     * @param service
-     */
-    @Override
-    public void addDiscoveredService( DiscoveredService service )
-    {
-        discoveredServices.add( service );
-    }
-
-    /**
-     * Removes it from the list.
-     * <p>
-     * @param service
-     */
-    @Override
-    public void removeDiscoveredService( DiscoveredService service )
-    {
-        discoveredServices.remove( service );
-    }
-
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/discovery/UDPDiscoverySenderUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/discovery/UDPDiscoverySenderUnitTest.java
deleted file mode 100644
index c28f805..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/discovery/UDPDiscoverySenderUnitTest.java
+++ /dev/null
@@ -1,155 +0,0 @@
-package org.apache.commons.jcs.utils.discovery;
-
-import java.util.ArrayList;
-
-import org.apache.commons.jcs.utils.discovery.UDPDiscoveryMessage.BroadcastType;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-
-/**
- * Tests for the sender.
- */
-public class UDPDiscoverySenderUnitTest
-    extends TestCase
-{
-    /** multicast address to send/receive on */
-    private static final String ADDRESS = "228.4.5.9";
-
-    /** multicast address to send/receive on */
-    private static final int PORT = 5556;
-
-    /** imaginary host address for sending */
-    private static final String SENDING_HOST = "imaginary host address";
-
-    /** imaginary port for sending */
-    private static final int SENDING_PORT = 1;
-
-    /** receiver instance for tests */
-    private UDPDiscoveryReceiver receiver;
-
-    /** sender instance for tests */
-    private UDPDiscoverySender sender;
-
-    /**
-     * Set up the receiver. Maybe better to just code sockets here? Set up the sender for sending
-     * the message.
-     * <p>
-     * @throws Exception on error
-     */
-    @Override
-    protected void setUp()
-        throws Exception
-    {
-        super.setUp();
-        receiver = new UDPDiscoveryReceiver( null, null, ADDRESS, PORT );
-        sender = new UDPDiscoverySender( ADDRESS, PORT, 0 );
-    }
-
-    /**
-     * Kill off the sender and receiver.
-     * <p>
-     * @throws Exception on error
-     */
-    @Override
-    protected void tearDown()
-        throws Exception
-    {
-        receiver.shutdown();
-        sender.close();
-        super.tearDown();
-    }
-
-    /**
-     * Test sending a live messages.
-     * <p>
-     * @throws Exception on error
-     */
-    public void testPassiveBroadcast()
-        throws Exception
-    {
-        // SETUP
-        ArrayList<String> cacheNames = new ArrayList<>();
-
-        // DO WORK
-        sender.passiveBroadcast( SENDING_HOST, SENDING_PORT, cacheNames, 1L );
-
-        // VERIFY
-        // grab the sent message
-        Object obj = receiver.waitForMessage() ;
-
-        assertTrue( "unexpected crap received", obj instanceof UDPDiscoveryMessage );
-
-        UDPDiscoveryMessage msg = (UDPDiscoveryMessage) obj;
-        // disabled test because of JCS-89
-        // assertEquals( "wrong host", SENDING_HOST, msg.getHost() );
-        assertEquals( "wrong port", SENDING_PORT, msg.getPort() );
-        assertEquals( "wrong message type", BroadcastType.PASSIVE, msg.getMessageType() );
-    }
-
-    /**
-     * Test sending a remove broadcast.
-     * <p>
-     * @throws Exception on error
-     */
-    public void testRemoveBroadcast()
-        throws Exception
-    {
-        // SETUP
-        ArrayList<String> cacheNames = new ArrayList<>();
-
-        // DO WORK
-        sender.removeBroadcast( SENDING_HOST, SENDING_PORT, cacheNames, 1L );
-
-        // VERIFY
-        // grab the sent message
-        Object obj = receiver.waitForMessage();
-
-        assertTrue( "unexpected crap received", obj instanceof UDPDiscoveryMessage );
-
-        UDPDiscoveryMessage msg = (UDPDiscoveryMessage) obj;
-        // disabled test because of JCS-89
-        // assertEquals( "wrong host", SENDING_HOST, msg.getHost() );
-        assertEquals( "wrong port", SENDING_PORT, msg.getPort() );
-        assertEquals( "wrong message type", BroadcastType.REMOVE, msg.getMessageType() );
-    }
-
-    /**
-     * Test sending a request broadcast.
-     * <p>
-     * @throws Exception on error
-     */
-    public void testRequestBroadcast()
-        throws Exception
-    {
-        // DO WORK
-        sender.requestBroadcast();
-
-        // VERIFY
-        // grab the sent message
-        Object obj = receiver.waitForMessage();
-
-        assertTrue( "unexpected crap received", obj instanceof UDPDiscoveryMessage );
-
-        UDPDiscoveryMessage msg = (UDPDiscoveryMessage) obj;
-        assertEquals( "wrong message type", BroadcastType.REQUEST, msg.getMessageType() );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/discovery/UDPDiscoveryServiceUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/discovery/UDPDiscoveryServiceUnitTest.java
deleted file mode 100644
index 054c152..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/discovery/UDPDiscoveryServiceUnitTest.java
+++ /dev/null
@@ -1,226 +0,0 @@
-package org.apache.commons.jcs.utils.discovery;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.ArrayList;
-
-import junit.framework.TestCase;
-
-/** Unit tests for the service. */
-public class UDPDiscoveryServiceUnitTest
-    extends TestCase
-{
-    /** Verify that the list is updated. */
-    public void testAddOrUpdateService_NotInList()
-    {
-        // SETUP
-        String host = "228.5.6.7";
-        int port = 6789;
-        UDPDiscoveryAttributes attributes = new UDPDiscoveryAttributes();
-        attributes.setUdpDiscoveryAddr( host );
-        attributes.setUdpDiscoveryPort( port );
-        attributes.setServicePort( 1000 );
-
-        // create the service
-        UDPDiscoveryService service = new UDPDiscoveryService( attributes );
-        service.startup();
-        service.addParticipatingCacheName( "testCache1" );
-
-        MockDiscoveryListener discoveryListener = new MockDiscoveryListener();
-        service.addDiscoveryListener( discoveryListener );
-
-        DiscoveredService discoveredService = new DiscoveredService();
-        discoveredService.setServiceAddress( host );
-        discoveredService.setCacheNames( new ArrayList<>() );
-        discoveredService.setServicePort( 1000 );
-        discoveredService.setLastHearFromTime( 100 );
-
-        // DO WORK
-        service.addOrUpdateService( discoveredService );
-
-        // VERIFY
-        assertTrue( "Service should be in the service list.", service.getDiscoveredServices()
-            .contains( discoveredService ) );
-        assertTrue( "Service should be in the listener list.", discoveryListener.discoveredServices
-            .contains( discoveredService ) );
-    }
-
-    /** Verify that the list is updated. */
-    public void testAddOrUpdateService_InList_NamesDoNotChange()
-    {
-        // SETUP
-        String host = "228.5.6.7";
-        int port = 6789;
-        UDPDiscoveryAttributes attributes = new UDPDiscoveryAttributes();
-        attributes.setUdpDiscoveryAddr( host );
-        attributes.setUdpDiscoveryPort( port );
-        attributes.setServicePort( 1000 );
-
-        // create the service
-        UDPDiscoveryService service = new UDPDiscoveryService( attributes );
-        service.startup();
-        service.addParticipatingCacheName( "testCache1" );
-
-        MockDiscoveryListener discoveryListener = new MockDiscoveryListener();
-        service.addDiscoveryListener( discoveryListener );
-
-        ArrayList<String> sametCacheNames = new ArrayList<>();
-        sametCacheNames.add( "name1" );
-
-        DiscoveredService discoveredService = new DiscoveredService();
-        discoveredService.setServiceAddress( host );
-        discoveredService.setCacheNames( sametCacheNames );
-        discoveredService.setServicePort( 1000 );
-        discoveredService.setLastHearFromTime( 100 );
-
-
-        DiscoveredService discoveredService2 = new DiscoveredService();
-        discoveredService2.setServiceAddress( host );
-        discoveredService2.setCacheNames( sametCacheNames );
-        discoveredService2.setServicePort( 1000 );
-        discoveredService2.setLastHearFromTime( 500 );
-
-        // DO WORK
-        service.addOrUpdateService( discoveredService );
-        // again
-        service.addOrUpdateService( discoveredService2 );
-
-        // VERIFY
-        assertEquals( "Should only be one in the set.", 1, service.getDiscoveredServices().size() );
-        assertTrue( "Service should be in the service list.", service.getDiscoveredServices()
-            .contains( discoveredService ) );
-        assertTrue( "Service should be in the listener list.", discoveryListener.discoveredServices
-            .contains( discoveredService ) );
-
-        // need to update the time this sucks. add has no effect convert to a map
-        for (DiscoveredService service1 : service.getDiscoveredServices())
-        {
-            if ( discoveredService.equals( service1 ) )
-            {
-                assertEquals( "The match should have the new last heard from time.", service1.getLastHearFromTime(),
-                              discoveredService2.getLastHearFromTime() );
-            }
-        }
-        // the mock has a list from all add calls.
-        // it should have been called when the list changed.
-        //assertEquals( "Mock should have been called once.", 1, discoveryListener.discoveredServices.size() );
-        // logic changed.  it's called every time.
-        assertEquals( "Mock should have been called twice.", 2, discoveryListener.discoveredServices.size() );
-    }
-
-    /** Verify that the list is updated. */
-    public void testAddOrUpdateService_InList_NamesChange()
-    {
-        // SETUP
-        String host = "228.5.6.7";
-        int port = 6789;
-        UDPDiscoveryAttributes attributes = new UDPDiscoveryAttributes();
-        attributes.setUdpDiscoveryAddr( host );
-        attributes.setUdpDiscoveryPort( port );
-        attributes.setServicePort( 1000 );
-
-        // create the service
-        UDPDiscoveryService service = new UDPDiscoveryService( attributes );
-        service.startup();
-        service.addParticipatingCacheName( "testCache1" );
-
-        MockDiscoveryListener discoveryListener = new MockDiscoveryListener();
-        service.addDiscoveryListener( discoveryListener );
-
-        DiscoveredService discoveredService = new DiscoveredService();
-        discoveredService.setServiceAddress( host );
-        discoveredService.setCacheNames( new ArrayList<>() );
-        discoveredService.setServicePort( 1000 );
-        discoveredService.setLastHearFromTime( 100 );
-
-        ArrayList<String> differentCacheNames = new ArrayList<>();
-        differentCacheNames.add( "name1" );
-        DiscoveredService discoveredService2 = new DiscoveredService();
-        discoveredService2.setServiceAddress( host );
-        discoveredService2.setCacheNames( differentCacheNames );
-        discoveredService2.setServicePort( 1000 );
-        discoveredService2.setLastHearFromTime( 500 );
-
-        // DO WORK
-        service.addOrUpdateService( discoveredService );
-        // again
-        service.addOrUpdateService( discoveredService2 );
-
-        // VERIFY
-        assertEquals( "Should only be one in the set.", 1, service.getDiscoveredServices().size() );
-        assertTrue( "Service should be in the service list.", service.getDiscoveredServices()
-            .contains( discoveredService ) );
-        assertTrue( "Service should be in the listener list.", discoveryListener.discoveredServices
-            .contains( discoveredService ) );
-
-        // need to update the time this sucks. add has no effect convert to a map
-        for (DiscoveredService service1 : service.getDiscoveredServices())
-        {
-            if ( discoveredService.equals( service1 ) )
-            {
-                assertEquals( "The match should have the new last heard from time.", service1.getLastHearFromTime(),
-                              discoveredService2.getLastHearFromTime() );
-                assertEquals( "The names should be updated.", service1.getCacheNames() + "", differentCacheNames + "" );
-            }
-        }
-        // the mock has a list from all add calls.
-        // it should have been called when the list changed.
-        assertEquals( "Mock should have been called twice.", 2, discoveryListener.discoveredServices.size() );
-        assertEquals( "The second mock listener add should be discoveredService2", discoveredService2,
-                      discoveryListener.discoveredServices.get( 1 ) );
-    }
-
-    /** Verify that the list is updated. */
-    public void testRemoveDiscoveredService()
-    {
-        // SETUP
-        String host = "228.5.6.7";
-        int port = 6789;
-        UDPDiscoveryAttributes attributes = new UDPDiscoveryAttributes();
-        attributes.setUdpDiscoveryAddr( host );
-        attributes.setUdpDiscoveryPort( port );
-        attributes.setServicePort( 1000 );
-
-        // create the service
-        UDPDiscoveryService service = new UDPDiscoveryService( attributes );
-        service.startup();
-        service.addParticipatingCacheName( "testCache1" );
-
-        MockDiscoveryListener discoveryListener = new MockDiscoveryListener();
-        service.addDiscoveryListener( discoveryListener );
-
-        DiscoveredService discoveredService = new DiscoveredService();
-        discoveredService.setServiceAddress( host );
-        discoveredService.setCacheNames( new ArrayList<>() );
-        discoveredService.setServicePort( 1000 );
-        discoveredService.setLastHearFromTime( 100 );
-
-        service.addOrUpdateService( discoveredService );
-
-        // DO WORK
-        service.removeDiscoveredService( discoveredService );
-
-        // VERIFY
-        assertFalse( "Service should not be in the service list.", service.getDiscoveredServices()
-            .contains( discoveredService ) );
-        assertFalse( "Service should not be in the listener list.", discoveryListener.discoveredServices
-            .contains( discoveredService ) );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/discovery/UDPDiscoveryUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/discovery/UDPDiscoveryUnitTest.java
deleted file mode 100644
index 0f85cb1..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/discovery/UDPDiscoveryUnitTest.java
+++ /dev/null
@@ -1,97 +0,0 @@
-package org.apache.commons.jcs.utils.discovery;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.ArrayList;
-
-import org.apache.commons.jcs.utils.timing.SleepUtil;
-
-import junit.framework.TestCase;
-
-/**
- * Unit tests for discovery
- */
-public class UDPDiscoveryUnitTest
-    extends TestCase
-{
-    /**
-     * <p>
-     * @throws Exception
-     */
-    public void testSimpleUDPDiscovery()
-        throws Exception
-    {
-        UDPDiscoveryAttributes attributes = new UDPDiscoveryAttributes();
-        attributes.setUdpDiscoveryAddr( /*"FF7E:230::1234"*/ "228.5.6.7" );
-        attributes.setUdpDiscoveryPort( 6789 );
-        attributes.setServicePort( 1000 );
-
-        // create the service
-        UDPDiscoveryService service = new UDPDiscoveryService( attributes );
-        service.startup();
-        service.addParticipatingCacheName( "testCache1" );
-
-        MockDiscoveryListener discoveryListener = new MockDiscoveryListener();
-        service.addDiscoveryListener( discoveryListener );
-
-        // create a receiver with the service
-        UDPDiscoveryReceiver receiver = new UDPDiscoveryReceiver( service,
-                null,
-                attributes.getUdpDiscoveryAddr(),
-                attributes.getUdpDiscoveryPort() );
-        Thread t = new Thread( receiver );
-        t.start();
-
-        // create a sender
-        UDPDiscoverySender sender = new UDPDiscoverySender(
-                attributes.getUdpDiscoveryAddr(),
-                attributes.getUdpDiscoveryPort(),
-                4 /* datagram TTL */);
-
-        // create more names than we have no wait facades for
-        // the only one that gets added should be testCache1
-        ArrayList<String> cacheNames = new ArrayList<>();
-        int numJunk = 10;
-        for ( int i = 0; i < numJunk; i++ )
-        {
-            cacheNames.add( "junkCacheName" + i );
-        }
-        cacheNames.add( "testCache1" );
-
-        // send max messages
-        int max = 10;
-        int cnt = 0;
-        for ( ; cnt < max; cnt++ )
-        {
-            sender.passiveBroadcast( "localhost", 1111, cacheNames, 1 );
-            SleepUtil.sleepAtLeast( 20 );
-        }
-
-        SleepUtil.sleepAtLeast( 200 );
-
-        // check to see that we got 10 messages
-        //System.out.println( "Receiver count = " + receiver.getCnt() );
-
-        // request braodcasts change things.
-        assertTrue( "Receiver count [" + receiver.getCnt() + "] should be the at least the number sent [" + cnt + "].",
-                    cnt <= receiver.getCnt() );
-        sender.close();
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/net/HostNameUtilUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/net/HostNameUtilUnitTest.java
deleted file mode 100644
index 4f45704..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/net/HostNameUtilUnitTest.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package org.apache.commons.jcs.utils.net;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-
-import java.net.UnknownHostException;
-
-/** Tests for the host name util. */
-public class HostNameUtilUnitTest
-    extends TestCase
-{
-    /**
-     * It's nearly impossible to unit test the getLocalHostLANAddress method.
-     * <p>
-     * @throws UnknownHostException
-     */
-    public void testGetLocalHostAddress_Simple() throws UnknownHostException
-    {
-        // DO WORK
-        String result = HostNameUtil.getLocalHostAddress();
-
-        // VERIFY
-        //System.out.print( result );
-        assertNotNull( "Should have a host address.", result );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/props/PropertyLoader.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/props/PropertyLoader.java
deleted file mode 100644
index 99311b8..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/props/PropertyLoader.java
+++ /dev/null
@@ -1,160 +0,0 @@
-package org.apache.commons.jcs.utils.props;
-
-import java.io.IOException;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.InputStream;
-import java.util.Properties;
-
-/**
- * I modified this class to work with .ccf files in particular. I also removed
- * the resource bundle functionality.
- * <p>
- * A simple class for loading java.util.Properties backed by .ccf files deployed
- * as classpath resources. See individual methods for details.
- * <p>
- * The original source is from:
- * <p>
- * @author (C) <a
- *         href="http://www.javaworld.com/columns/jw-qna-index.shtml">Vlad
- *         Roubtsov </a>, 2003
- */
-public abstract class PropertyLoader
-{
-    /** throw an error if we can load the file */
-    private static final boolean THROW_ON_LOAD_FAILURE = true;
-
-    /** File suffix. */
-    private static final String SUFFIX = ".ccf";
-
-    /** property suffix */
-    private static final String SUFFIX_PROPERTIES = ".properties";
-
-    /**
-     * Looks up a resource named 'name' in the classpath. The resource must map
-     * to a file with .ccf extention. The name is assumed to be absolute and can
-     * use either "/" or "." for package segment separation with an optional
-     * leading "/" and optional ".ccf" suffix.
-     * <p>
-     * The suffix ".ccf" will be appended if it is not set. This can also handle
-     * .properties files
-     * <p>
-     * Thus, the following names refer to the same resource:
-     *
-     * <pre>
-     *
-     *       some.pkg.Resource
-     *       some.pkg.Resource.ccf
-     *       some/pkg/Resource
-     *       some/pkg/Resource.ccf
-     *       /some/pkg/Resource
-     *       /some/pkg/Resource.ccf
-     * </pre>
-     *
-     * @param name
-     *            classpath resource name [may not be null]
-     * @param loader
-     *            classloader through which to load the resource [null is
-     *            equivalent to the application loader]
-     * @return resource converted to java.util.properties [may be null if the
-     *         resource was not found and THROW_ON_LOAD_FAILURE is false]
-     * @throws IllegalArgumentException
-     *             if the resource was not found and THROW_ON_LOAD_FAILURE is
-     *             true
-     */
-    public static Properties loadProperties( String name, ClassLoader loader )
-    {
-        boolean isCCFSuffix = true;
-
-        if ( name == null )
-        {
-            throw new IllegalArgumentException( "null input: name" );
-        }
-
-        ClassLoader classLoader = ( loader == null ) ? ClassLoader.getSystemClassLoader() : loader;
-
-        String fileName = name.startsWith( "/" ) ? name.substring( 1 ) : name;
-
-        if ( fileName.endsWith( SUFFIX ) )
-        {
-            fileName = fileName.substring( 0, fileName.length() - SUFFIX.length() );
-        }
-
-        if ( fileName.endsWith( SUFFIX_PROPERTIES ) )
-        {
-            fileName = fileName.substring( 0, fileName.length() - SUFFIX_PROPERTIES.length() );
-            isCCFSuffix = false;
-        }
-
-        fileName = fileName.replace( '.', '/' );
-
-        if ( !fileName.endsWith( SUFFIX ) && isCCFSuffix )
-        {
-            fileName = fileName.concat( SUFFIX );
-        }
-        else if ( !fileName.endsWith( SUFFIX_PROPERTIES ) && !isCCFSuffix )
-        {
-            fileName = fileName.concat( SUFFIX_PROPERTIES );
-        }
-
-        Properties result = null;
-
-        try (InputStream in = classLoader.getResourceAsStream( fileName ))
-        {
-            result = new Properties();
-            result.load( in ); // can throw IOException
-        }
-        catch ( IOException e )
-        {
-            result = null;
-        }
-
-        if ( THROW_ON_LOAD_FAILURE && result == null )
-        {
-            throw new IllegalArgumentException( "could not load [" + fileName + "]" + " as " + "a classloader resource" );
-        }
-
-        return result;
-    }
-
-    /**
-     * A convenience overload of {@link #loadProperties(String, ClassLoader)}
-     * that uses the current thread's context classloader. A better strategy
-     * would be to use techniques shown in
-     * http://www.javaworld.com/javaworld/javaqa/2003-06/01-qa-0606-load.html
-     * <p>
-     * @param name
-     * @return Properties
-     */
-    public static Properties loadProperties( final String name )
-    {
-        return loadProperties( name, Thread.currentThread().getContextClassLoader() );
-    }
-
-    /**
-     * Can't use this one.
-     */
-    private PropertyLoader()
-    {
-        super();
-    } // this class is not extentible
-
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/serialization/CompressingSerializerUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/serialization/CompressingSerializerUnitTest.java
deleted file mode 100644
index 6b09dca..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/serialization/CompressingSerializerUnitTest.java
+++ /dev/null
@@ -1,119 +0,0 @@
-package org.apache.commons.jcs.utils.serialization;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-
-import java.io.IOException;
-
-/**
- * Tests the compressing serializer.
- */
-public class CompressingSerializerUnitTest
-    extends TestCase
-{
-    /**
-     * Verify that we don't get any erorrs for null input.
-     * <p>
-     * @throws ClassNotFoundException
-     * @throws IOException
-     */
-    public void testDeserialize_NullInput()
-        throws IOException, ClassNotFoundException
-    {
-        // SETUP
-        CompressingSerializer serializer = new CompressingSerializer();
-
-        // DO WORK
-        Object result = serializer.deSerialize( null, null );
-
-        // VERIFY
-        assertNull( "Should have nothing.", result );
-    }
-
-    /**
-     * Test simple back and forth with a string.
-     * <p>
-     * ))&lt;=&gt;((
-     * <p>
-     * @throws Exception on error
-     */
-    public void testSimpleBackAndForth()
-        throws Exception
-    {
-        // SETUP
-        CompressingSerializer serializer = new CompressingSerializer();
-
-        // DO WORK
-        String before = "adsfdsafdsafdsafdsafdsafdsafdsagfdsafdsafdsfdsafdsafsa333 31231";
-        String after = (String) serializer.deSerialize( serializer.serialize( before ), null );
-
-        // VERIFY
-        assertEquals( "Before and after should be the same.", before, after );
-    }
-
-    /**
-     * Test serialization with a null object. Verify that we don't get an error.
-     * <p>
-     * @throws Exception on error
-     */
-    public void testSerialize_NullInput()
-        throws Exception
-    {
-        // SETUP
-        CompressingSerializer serializer = new CompressingSerializer();
-
-        String before = null;
-
-        // DO WORK
-        byte[] serialized = serializer.serialize( before );
-        String after = (String) serializer.deSerialize( serialized, null );
-
-        // VERIFY
-        assertNull( "Should have nothing. after =" + after, after );
-    }
-
-    /**
-     * Verify that the compressed is smaller.
-     * <p>
-     * @throws Exception on error
-     */
-    public void testSerialize_CompareCompressedAndUncompressed()
-        throws Exception
-    {
-        // SETUP
-        CompressingSerializer serializer = new CompressingSerializer();
-
-        // I hate for loops.
-        String before = "adsfdsafdsafdsafdsafdsafdsafdsagfdsafdsafdssaf dsaf sadf dsaf dsaf dsaf "
-            + "dsafdsa fdsaf dsaf dsafdsa dsaf dsaf dsaf dsaf dsafdsa76f dsa798f dsa6fdsa 087f  "
-            + "gh 987dsahb dsahbuhbfnui nufdsa hbv87 f8vhdsgbnfv h8fdg8dfjvn8fdwgj fdsgjb9fdsjbv"
-            + "jvhjv hg98f-dsaghj j9fdsb gfsb 9fdshjbgb987fdsbfdwgh ujbhjbhb hbfdsgh fdshb "
-            + "Ofdsgyfesgyfdsafdsafsa333 31231";
-
-        // DO WORK
-        byte[] compressed = serializer.serialize( before );
-        byte[] nonCompressed = new StandardSerializer().serialize( before );
-
-        // VERIFY
-        assertTrue( "Compressed should be smaller. compressed size = " + compressed.length + "nonCompressed size = "
-            + nonCompressed.length, compressed.length < nonCompressed.length );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/serialization/SerializationConversionUtilUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/serialization/SerializationConversionUtilUnitTest.java
deleted file mode 100644
index 9f8b6a1..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/serialization/SerializationConversionUtilUnitTest.java
+++ /dev/null
@@ -1,195 +0,0 @@
-package org.apache.commons.jcs.utils.serialization;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.ElementAttributes;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICacheElementSerialized;
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-
-import java.io.IOException;
-
-/**
- * Tests the serialization conversion util.
- * <p>
- * @author Aaron Smuts
- */
-public class SerializationConversionUtilUnitTest
-    extends TestCase
-{
-    /**
-     * Verify null for null.
-     * <p>
-     * @throws IOException
-     */
-    public void testgGetSerializedCacheElement_null()
-        throws IOException
-    {
-        // SETUP
-        IElementSerializer elementSerializer = new StandardSerializer();
-        ICacheElement<String, String> before = null;
-
-        // DO WORK
-        ICacheElementSerialized<String, String> result =
-            SerializationConversionUtil.getSerializedCacheElement( before, elementSerializer );
-
-        // VERIFY
-        assertNull( "Should get null for null", result );
-    }
-
-    /**
-     * Verify null for null.
-     * <p>
-     * @throws Exception
-     */
-    public void testgGetDeSerializedCacheElement_null()
-        throws Exception
-    {
-        // SETUP
-        IElementSerializer elementSerializer = new StandardSerializer();
-        ICacheElementSerialized<String, String> before = null;
-
-        // DO WORK
-        ICacheElement<String, String> result =
-            SerializationConversionUtil.getDeSerializedCacheElement( before, elementSerializer );
-
-        // VERIFY
-        assertNull( "Should get null for null", result );
-    }
-
-    /**
-     * Verify that we can go back and forth with the simplest of objects.
-     * <p>
-     * @throws Exception
-     */
-    public void testSimpleConversion()
-        throws Exception
-    {
-        // SETUP
-        String cacheName = "testName";
-        String key = "key";
-        String value = "value fdsadf dsafdsa fdsaf dsafdsaf dsafdsaf dsaf dsaf dsaf dsafa dsaf dsaf dsafdsaf";
-
-        IElementSerializer elementSerializer = new StandardSerializer();
-
-        IElementAttributes attr = new ElementAttributes();
-        attr.setMaxLife(34);
-
-        ICacheElement<String, String> before = new CacheElement<>( cacheName, key, value );
-        before.setElementAttributes( attr );
-
-        // DO WORK
-        ICacheElementSerialized<String, String> serialized =
-            SerializationConversionUtil.getSerializedCacheElement( before, elementSerializer );
-
-        // VERIFY
-        assertNotNull( "Should have a serialized object.", serialized );
-
-        // DO WORK
-        ICacheElement<String, String> after =
-            SerializationConversionUtil.getDeSerializedCacheElement( serialized, elementSerializer );
-
-        // VERIFY
-        assertNotNull( "Should have a deserialized object.", after );
-        assertEquals( "Values should be the same.", before.getVal(), after.getVal() );
-        assertEquals( "Attributes should be the same.", before.getElementAttributes().getMaxLife(), after
-            .getElementAttributes().getMaxLife() );
-        assertEquals( "Keys should be the same.", before.getKey(), after.getKey() );
-        assertEquals( "Cache name should be the same.", before.getCacheName(), after.getCacheName() );
-    }
-
-    /**
-     * Verify that we can go back and forth with the simplest of objects.
-     *<p>
-     * @throws Exception
-     */
-    public void testAccidentalDoubleConversion()
-        throws Exception
-    {
-        // SETUP
-        String cacheName = "testName";
-        String key = "key";
-        String value = "value fdsadf dsafdsa fdsaf dsafdsaf dsafdsaf dsaf dsaf dsaf dsafa dsaf dsaf dsafdsaf";
-
-        IElementSerializer elementSerializer = new StandardSerializer();
-
-        IElementAttributes attr = new ElementAttributes();
-        attr.setMaxLife(34);
-
-        ICacheElement<String, String> before = new CacheElement<>( cacheName, key, value );
-        before.setElementAttributes( attr );
-
-        // DO WORK
-        ICacheElementSerialized<String, String> alreadySerialized =
-            SerializationConversionUtil.getSerializedCacheElement( before, elementSerializer );
-        ICacheElementSerialized<String, String> serialized =
-            SerializationConversionUtil.getSerializedCacheElement( alreadySerialized, elementSerializer );
-
-        // VERIFY
-        assertNotNull( "Should have a serialized object.", serialized );
-
-        // DO WORK
-        ICacheElement<String, String> after =
-            SerializationConversionUtil.getDeSerializedCacheElement( serialized, elementSerializer );
-
-        // VERIFY
-        assertNotNull( "Should have a deserialized object.", after );
-        assertEquals( "Values should be the same.", before.getVal(), after.getVal() );
-        assertEquals( "Attributes should be the same.", before.getElementAttributes().getMaxLife(), after
-            .getElementAttributes().getMaxLife() );
-        assertEquals( "Keys should be the same.", before.getKey(), after.getKey() );
-        assertEquals( "Cache name should be the same.", before.getCacheName(), after.getCacheName() );
-    }
-
-    /**
-     * Verify that we get an IOException for a null serializer.
-     */
-    public void testNullSerializerConversion()
-    {
-        // SETUP
-        String cacheName = "testName";
-        String key = "key";
-        String value = "value fdsadf dsafdsa fdsaf dsafdsaf dsafdsaf dsaf dsaf dsaf dsafa dsaf dsaf dsafdsaf";
-
-        IElementSerializer elementSerializer = null;// new StandardSerializer();
-
-        IElementAttributes attr = new ElementAttributes();
-        attr.setMaxLife(34);
-
-        ICacheElement<String, String> before = new CacheElement<>( cacheName, key, value );
-        before.setElementAttributes( attr );
-
-        // DO WORK
-        try
-        {
-            SerializationConversionUtil.getSerializedCacheElement( before, elementSerializer );
-
-            // VERIFY
-            fail( "We should have received an IOException." );
-        }
-        catch ( IOException e )
-        {
-            // expected
-        }
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/serialization/StandardSerializerUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/serialization/StandardSerializerUnitTest.java
deleted file mode 100644
index 0411fec..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/serialization/StandardSerializerUnitTest.java
+++ /dev/null
@@ -1,102 +0,0 @@
-package org.apache.commons.jcs.utils.serialization;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-
-/**
- * Tests the standard serializer.
- *<p>
- * @author Aaron Smuts
- */
-public class StandardSerializerUnitTest
-    extends TestCase
-{
-    /**
-     * Test simple back and forth with a string.
-     *<p>
-     * @throws Exception
-     */
-    public void testSimpleBackAndForth()
-        throws Exception
-    {
-        // SETUP
-        StandardSerializer serializer = new StandardSerializer();
-
-        String before = "adsfdsafdsafdsafdsafdsafdsafdsagfdsafdsafdsfdsafdsafsa333 31231";
-
-        // DO WORK
-        String after = (String) serializer.deSerialize( serializer.serialize( before ), null );
-
-        // VERIFY
-        assertEquals( "Before and after should be the same.", before, after );
-    }
-
-    /**
-     * Test serialization with a null object. Verify that we don't get an error.
-     *<p>
-     * @throws Exception
-     */
-    public void testNullInput()
-        throws Exception
-    {
-        // SETUP
-        StandardSerializer serializer = new StandardSerializer();
-
-        String before = null;
-
-        // DO WORK
-        byte[] serialized = serializer.serialize( before );
-        //System.out.println( "testNullInput " + serialized );
-
-        String after = (String) serializer.deSerialize( serialized, null );
-        //System.out.println( "testNullInput " + after );
-
-        // VERIFY
-        assertNull( "Should have nothing.", after );
-    }
-
-    /**
-     * Test simple back and forth with a string.
-     *<p>
-     * @throws Exception
-     */
-    public void testBigStringBackAndForth()
-        throws Exception
-    {
-        // SETUP
-        StandardSerializer serializer = new StandardSerializer();
-
-        String string = "This is my big string ABCDEFGH";
-        StringBuilder sb = new StringBuilder();
-        sb.append( string );
-        for ( int i = 0; i < 4; i++ )
-        {
-            sb.append( " " + i + sb.toString() ); // big string
-        }
-        String before = sb.toString();
-
-        // DO WORK
-        String after = (String) serializer.deSerialize( serializer.serialize( before ), null );
-
-        // VERIFY
-        assertEquals( "Before and after should be the same.", before, after );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/struct/DoubleLinkedListDumpUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/struct/DoubleLinkedListDumpUnitTest.java
deleted file mode 100644
index 644e4c3..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/struct/DoubleLinkedListDumpUnitTest.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package org.apache.commons.jcs.utils.struct;
-
-import java.io.StringWriter;
-
-import org.apache.commons.jcs.TestLogConfigurationUtil;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-
-/** Unit tests for the double linked list. */
-public class DoubleLinkedListDumpUnitTest
-    extends TestCase
-{
-    /** verify that the entries are dumped. */
-    public void testDumpEntries_DebugTrue()
-    {
-        // SETUP
-        StringWriter stringWriter = new StringWriter();
-        TestLogConfigurationUtil.configureLogger( stringWriter, DoubleLinkedList.class.getName() );
-
-        DoubleLinkedList<DoubleLinkedListNode<String>> list = new DoubleLinkedList<>();
-
-        String payload1 = "payload1";
-        DoubleLinkedListNode<String> node1 = new DoubleLinkedListNode<>( payload1 );
-
-        String payload2 = "payload2";
-        DoubleLinkedListNode<String> node2 = new DoubleLinkedListNode<>( payload2 );
-
-        list.addLast( node1 );
-        list.addLast( node2 );
-        list.debugDumpEntries();
-
-        // WO WORK
-        String result = stringWriter.toString();
-
-        // VERIFY
-        assertTrue( "Missing node in log dump", result.indexOf( payload1 ) != -1 );
-        assertTrue( "Missing node in log dump", result.indexOf( payload2 ) != -1 );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/struct/DoubleLinkedListUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/struct/DoubleLinkedListUnitTest.java
deleted file mode 100644
index a215598..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/struct/DoubleLinkedListUnitTest.java
+++ /dev/null
@@ -1,159 +0,0 @@
-package org.apache.commons.jcs.utils.struct;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-
-/** Unit tests for the double linked list. */
-public class DoubleLinkedListUnitTest
-    extends TestCase
-{
-    /** verify that the last is added when the list is empty. */
-    public void testAddLast_Empty()
-    {
-        // SETUP
-        DoubleLinkedList<DoubleLinkedListNode<String>> list = new DoubleLinkedList<>();
-
-        String payload1 = "payload1";
-        DoubleLinkedListNode<String> node1 = new DoubleLinkedListNode<>( payload1 );
-
-        // WO WORK
-        list.addLast( node1 );
-
-        // VERIFY
-        assertEquals( "Wrong last", node1, list.getLast() );
-    }
-
-    /** verify that the last is added when the list is empty. */
-    public void testAddLast_NotEmpty()
-    {
-        // SETUP
-        DoubleLinkedList<DoubleLinkedListNode<String>> list = new DoubleLinkedList<>();
-
-        String payload1 = "payload1";
-        DoubleLinkedListNode<String> node1 = new DoubleLinkedListNode<>( payload1 );
-
-        String payload2 = "payload2";
-        DoubleLinkedListNode<String> node2 = new DoubleLinkedListNode<>( payload2 );
-
-        // WO WORK
-        list.addLast( node1 );
-        list.addLast( node2 );
-
-        // VERIFY
-        assertEquals( "Wrong last", node2, list.getLast() );
-    }
-
-    /** verify that it's added last. */
-    public void testMakeLast_wasFirst()
-    {
-        // SETUP
-        DoubleLinkedList<DoubleLinkedListNode<String>> list = new DoubleLinkedList<>();
-
-        String payload1 = "payload1";
-        DoubleLinkedListNode<String> node1 = new DoubleLinkedListNode<>( payload1 );
-
-        String payload2 = "payload2";
-        DoubleLinkedListNode<String> node2 = new DoubleLinkedListNode<>( payload2 );
-
-        list.addFirst( node2 );
-        list.addFirst(  node1 );
-
-        // DO WORK
-        list.makeLast( node1 );
-
-        // VERIFY
-        assertEquals( "Wrong size", 2, list.size() );
-        assertEquals( "Wrong last", node1, list.getLast() );
-        assertEquals( "Wrong first", node2, list.getFirst() );
-    }
-
-    /** verify that it's added last. */
-    public void testMakeLast_wasLast()
-    {
-        // SETUP
-        DoubleLinkedList<DoubleLinkedListNode<String>> list = new DoubleLinkedList<>();
-
-        String payload1 = "payload1";
-        DoubleLinkedListNode<String> node1 = new DoubleLinkedListNode<>( payload1 );
-
-        String payload2 = "payload2";
-        DoubleLinkedListNode<String> node2 = new DoubleLinkedListNode<>( payload2 );
-
-        list.addFirst( node1 );
-        list.addFirst(  node2 );
-
-        // DO WORK
-        list.makeLast( node1 );
-
-        // VERIFY
-        assertEquals( "Wrong size", 2, list.size() );
-        assertEquals( "Wrong last", node1, list.getLast() );
-        assertEquals( "Wrong first", node2, list.getFirst() );
-    }
-
-    /** verify that it's added last. */
-    public void testMakeLast_wasAlone()
-    {
-        // SETUP
-        DoubleLinkedList<DoubleLinkedListNode<String>> list = new DoubleLinkedList<>();
-
-        String payload1 = "payload1";
-        DoubleLinkedListNode<String> node1 = new DoubleLinkedListNode<>( payload1 );
-
-        list.addFirst( node1 );
-
-        // DO WORK
-        list.makeLast( node1 );
-
-        // VERIFY
-        assertEquals( "Wrong size", 1, list.size() );
-        assertEquals( "Wrong last", node1, list.getLast() );
-        assertEquals( "Wrong first", node1, list.getFirst() );
-    }
-
-    /** verify that it's added last. */
-    public void testMakeLast_wasInMiddle()
-    {
-        // SETUP
-        DoubleLinkedList<DoubleLinkedListNode<String>> list = new DoubleLinkedList<>();
-
-        String payload1 = "payload1";
-        DoubleLinkedListNode<String> node1 = new DoubleLinkedListNode<>( payload1 );
-
-        String payload2 = "payload2";
-        DoubleLinkedListNode<String> node2 = new DoubleLinkedListNode<>( payload2 );
-
-        String payload3 = "payload3";
-        DoubleLinkedListNode<String> node3 = new DoubleLinkedListNode<>( payload3 );
-
-        list.addFirst( node2 );
-        list.addFirst(  node1 );
-        list.addFirst(  node3 );
-
-        // DO WORK
-        list.makeLast( node1 );
-
-        // VERIFY
-        assertEquals( "Wrong size", 3, list.size() );
-        assertEquals( "Wrong last", node1, list.getLast() );
-        assertEquals( "Wrong first", node3, list.getFirst() );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/struct/JCSvsCommonsLRUMapPerformanceTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/struct/JCSvsCommonsLRUMapPerformanceTest.java
deleted file mode 100644
index eca6224..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/struct/JCSvsCommonsLRUMapPerformanceTest.java
+++ /dev/null
@@ -1,179 +0,0 @@
-package org.apache.commons.jcs.utils.struct;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.Map;
-
-import org.apache.commons.jcs.log.Log;
-import org.apache.commons.jcs.log.LogManager;
-
-import junit.framework.TestCase;
-
-/**
- * This ensures that the jcs version of the LRU map is as fast as the commons
- * version. It has been testing at .6 to .7 times the commons LRU.
- *
- */
-public class JCSvsCommonsLRUMapPerformanceTest
-    extends TestCase
-{
-    /** jcs / commons */
-    float ratioPut = 0;
-
-    /** jcs / commons */
-    float ratioGet = 0;
-
-    /** goal */
-    float target = 1.0f;
-
-    /** loops */
-    int loops = 20;
-
-    /** number to test with */
-    int tries = 100000;
-
-    /**
-     * A unit test for JUnit
-     *
-     * @throws Exception
-     *                Description of the Exception
-     */
-    public void testSimpleLoad()
-        throws Exception
-    {
-        Log log = LogManager.getLog( LRUMap.class );
-        if ( log.isDebugEnabled() )
-        {
-            System.out.println( "The log level must be at info or above for the a performance test." );
-            return;
-        }
-
-        doWork();
-        assertTrue( this.ratioPut < target );
-        assertTrue( this.ratioGet < target );
-    }
-
-    /**
-     *
-     */
-    public void doWork()
-    {
-        long start = 0;
-        long end = 0;
-        long time = 0;
-        float tPer = 0;
-
-        long putTotalJCS = 0;
-        long getTotalJCS = 0;
-        long putTotalHashtable = 0;
-        long getTotalHashtable = 0;
-
-        String name = "LRUMap";
-        String cache2Name = "";
-
-        try
-        {
-            Map<String, String> cache = new LRUMap<>( tries );
-
-            for ( int j = 0; j < loops; j++ )
-            {
-                name = "JCS      ";
-                start = System.currentTimeMillis();
-                for ( int i = 0; i < tries; i++ )
-                {
-                    cache.put( "key:" + i, "data" + i );
-                }
-                end = System.currentTimeMillis();
-                time = end - start;
-                putTotalJCS += time;
-                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
-                System.out.println( name + " put time for " + tries + " = " + time + "; millis per = " + tPer );
-
-                start = System.currentTimeMillis();
-                for ( int i = 0; i < tries; i++ )
-                {
-                    cache.get( "key:" + i );
-                }
-                end = System.currentTimeMillis();
-                time = end - start;
-                getTotalJCS += time;
-                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
-                System.out.println( name + " get time for " + tries + " = " + time + "; millis per = " + tPer );
-
-                // /////////////////////////////////////////////////////////////
-                cache2Name = "Commons  ";
-                // or LRUMapJCS
-                Map<String, String> cache2 = new org.apache.commons.collections4.map.LRUMap<String, String>( tries );
-                // cache2Name = "Hashtable";
-                // Hashtable cache2 = new Hashtable();
-                start = System.currentTimeMillis();
-                for ( int i = 0; i < tries; i++ )
-                {
-                    cache2.put( "key:" + i, "data" + i );
-                }
-                end = System.currentTimeMillis();
-                time = end - start;
-                putTotalHashtable += time;
-                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
-                System.out.println( cache2Name + " put time for " + tries + " = " + time + "; millis per = " + tPer );
-
-                start = System.currentTimeMillis();
-                for ( int i = 0; i < tries; i++ )
-                {
-                    cache2.get( "key:" + i );
-                }
-                end = System.currentTimeMillis();
-                time = end - start;
-                getTotalHashtable += time;
-                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
-                System.out.println( cache2Name + " get time for " + tries + " = " + time + "; millis per = " + tPer );
-
-                System.out.println( "\n" );
-            }
-
-        }
-        catch ( Exception e )
-        {
-            e.printStackTrace( System.out );
-            System.out.println( e );
-        }
-
-        long putAvJCS = putTotalJCS / loops;
-        long getAvJCS = getTotalJCS / loops;
-        long putAvHashtable = putTotalHashtable / loops;
-        long getAvHashtable = getTotalHashtable / loops;
-
-        System.out.println( "Finished " + loops + " loops of " + tries + " gets and puts" );
-
-        System.out.println( "\n" );
-        System.out.println( "Put average for LRUMap       = " + putAvJCS );
-        System.out.println( "Put average for " + cache2Name + " = " + putAvHashtable );
-        ratioPut = Float.intBitsToFloat( (int) putAvJCS ) / Float.intBitsToFloat( (int) putAvHashtable );
-        System.out.println( name + " puts took " + ratioPut + " times the " + cache2Name + ", the goal is <" + target
-            + "x" );
-
-        System.out.println( "\n" );
-        System.out.println( "Get average for LRUMap       = " + getAvJCS );
-        System.out.println( "Get average for " + cache2Name + " = " + getAvHashtable );
-        ratioGet = Float.intBitsToFloat( (int) getAvJCS ) / Float.intBitsToFloat( (int) getAvHashtable );
-        System.out.println( name + " gets took " + ratioGet + " times the " + cache2Name + ", the goal is <" + target
-            + "x" );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/struct/LRUMapConcurrentUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/struct/LRUMapConcurrentUnitTest.java
deleted file mode 100644
index 5894fe4..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/struct/LRUMapConcurrentUnitTest.java
+++ /dev/null
@@ -1,282 +0,0 @@
-package org.apache.commons.jcs.utils.struct;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.Iterator;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestSuite;
-
-/**
- * Tests the LRUMap
- *
- */
-public class LRUMapConcurrentUnitTest
-    extends TestCase
-{
-    /** number to test with */
-    private static int items = 20000;
-
-    /**
-     * Constructor for the TestSimpleLoad object
-     * <p>
-     * @param testName
-     *            Description of the Parameter
-     */
-    public LRUMapConcurrentUnitTest( String testName )
-    {
-        super( testName );
-    }
-
-    /**
-     * A unit test suite for JUnit
-     * <p>
-     * @return The test suite
-     */
-    public static Test suite()
-    {
-        // run the basic tests
-        TestSuite suite = new TestSuite( LRUMapConcurrentUnitTest.class );
-
-        // run concurrent tests
-        final LRUMap<String, String> map = new LRUMap<>( 2000 );
-        suite.addTest( new LRUMapConcurrentUnitTest( "conc1" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runConcurrentPutGetTests( map, 2000 );
-            }
-        } );
-        suite.addTest( new LRUMapConcurrentUnitTest( "conc2" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runConcurrentPutGetTests( map, 2000 );
-            }
-        } );
-        suite.addTest( new LRUMapConcurrentUnitTest( "conc3" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runConcurrentPutGetTests( map, 2000 );
-            }
-        } );
-
-        // run more concurrent tests
-        final int max2 = 20000;
-        final LRUMap<String, String> map2 = new LRUMap<>( max2 );
-        suite.addTest( new LRUMapConcurrentUnitTest( "concB1" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runConcurrentRangeTests( map2, 10000, max2 );
-            }
-        } );
-        suite.addTest( new LRUMapConcurrentUnitTest( "concB1" )
-        {
-            @Override
-            public void runTest()
-                throws Exception
-            {
-                this.runConcurrentRangeTests( map2, 0, 9999 );
-            }
-        } );
-
-        return suite;
-    }
-
-    /**
-     * Just test that we can put, get and remove as expected.
-     * <p>
-     * @throws Exception
-     *                Description of the Exception
-     */
-    public void testSimpleLoad()
-        throws Exception
-    {
-        LRUMap<String, String> map = new LRUMap<>( items );
-
-        for ( int i = 0; i < items; i++ )
-        {
-            map.put( i + ":key", "data" + i );
-        }
-
-        for ( int i = items - 1; i >= 0; i-- )
-        {
-            String res = map.get( i + ":key" );
-            assertNotNull( "[" + i + ":key] should not be null", res );
-        }
-
-        // test removal
-        map.remove( "300:key" );
-        assertNull( map.get( "300:key" ) );
-
-    }
-
-    /**
-     * Just make sure that the LRU functions in he most simple case.
-     *
-     * @throws Exception
-     *                Description of the Exception
-     */
-    public void testLRURemoval()
-        throws Exception
-    {
-        int total = 10;
-        LRUMap<String, String> map = new LRUMap<>( total );
-
-        // put the max in
-        for ( int i = 0; i < total; i++ )
-        {
-            map.put( i + ":key", "data" + i );
-        }
-
-        Iterator<?> it = map.entrySet().iterator();
-        while ( it.hasNext() )
-        {
-            assertNotNull( it.next() );
-        }
-//        System.out.println( map.getStatistics() );
-
-        // get the max out backwards
-        for ( int i = total - 1; i >= 0; i-- )
-        {
-            String res = map.get( i + ":key" );
-            assertNotNull( "[" + i + ":key] should not be null", res );
-        }
-
-//        System.out.println( map.getStatistics() );
-
-        //since we got them backwards the total should be at the end.
-        // add one confirm that total is gone.
-        map.put( ( total ) + ":key", "data" + ( total ) );
-        assertNull( map.get( ( total - 1 ) + ":key" ) );
-
-    }
-
-    /**
-     * @throws Exception
-     */
-    public void testLRURemovalAgain()
-        throws Exception
-    {
-        int total = 10000;
-        LRUMap<String, String> map = new LRUMap<>( total );
-
-        // put the max in
-        for ( int i = 0; i < total * 2; i++ )
-        {
-            map.put( i + ":key", "data" + i );
-        }
-
-        // get the total number, these should be null
-        for ( int i = total - 1; i >= 0; i-- )
-        {
-            assertNull( map.get( i + ":key" ) );
-        }
-
-        // get the total to total *2 items out, these should be found.
-        for ( int i = ( total * 2 ) - 1; i >= total; i-- )
-        {
-            String res = map.get( i + ":key" );
-            assertNotNull( "[" + i + ":key] should not be null", res );
-        }
-
-//        System.out.println( map.getStatistics() );
-    }
-
-    /**
-     * Just make sure that we can put and get concurrently
-     *
-     * @param map
-     * @param items
-     * @throws Exception
-     */
-    public void runConcurrentPutGetTests( LRUMap<String, String> map, int items )
-        throws Exception
-    {
-        for ( int i = 0; i < items; i++ )
-        {
-            map.put( i + ":key", "data" + i );
-        }
-
-        for ( int i = items - 1; i >= 0; i-- )
-        {
-            String res = map.get( i + ":key" );
-            assertNotNull( "[" + i + ":key] should not be null", res );
-        }
-    }
-
-    /**
-     * Put, get, and remove from a range. This should occur at a range that is
-     * not touched by other tests.
-     * <p>
-     * @param map
-     * @param start
-     * @param end
-     * @throws Exception
-     */
-    public void runConcurrentRangeTests( LRUMap<String, String> map, int start, int end )
-        throws Exception
-    {
-        for ( int i = start; i < end; i++ )
-        {
-            map.put( i + ":key", "data" + i );
-        }
-
-        for ( int i = end - 1; i >= start; i-- )
-        {
-            String res = map.get( i + ":key" );
-            assertNotNull( "[" + i + ":key] should not be null", res );
-        }
-
-        // test removal
-        map.remove( start + ":key" );
-        assertNull( map.get( start + ":key" ) );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/struct/LRUMapPerformanceTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/struct/LRUMapPerformanceTest.java
deleted file mode 100644
index fd57f82..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/struct/LRUMapPerformanceTest.java
+++ /dev/null
@@ -1,176 +0,0 @@
-package org.apache.commons.jcs.utils.struct;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.Map;
-
-import junit.framework.TestCase;
-
-/**
- * This ensures that the jcs version of the LRU map is as fast as the commons
- * version. It has been testing at .6 to .7 times the commons LRU.
- * <p>
- * @author aaronsm
- *
- */
-public class LRUMapPerformanceTest
-    extends TestCase
-{
-    /** The put put ration after the test */
-    float ratioPut = 0;
-
-    /** The ratio after the test */
-    float ratioGet = 0;
-
-    /** put jcs / commons ratio */
-    float targetPut = 1.2f;
-
-    /** get jcs / commons ratio */
-    float targetGet = .5f;
-
-    /** Time to loop */
-    int loops = 20;
-
-    /** items to put and get per loop */
-    int tries = 100000;
-
-    /**
-     * A unit test for JUnit
-     *
-     * @throws Exception
-     *                Description of the Exception
-     */
-    public void testSimpleLoad()
-        throws Exception
-    {
-        doWork();
-        assertTrue( this.ratioPut < targetPut );
-        assertTrue( this.ratioGet < targetGet );
-    }
-
-    /**
-     *
-     */
-    public void doWork()
-    {
-        long start = 0;
-        long end = 0;
-        long time = 0;
-        float tPer = 0;
-
-        long putTotalJCS = 0;
-        long getTotalJCS = 0;
-        long putTotalHashtable = 0;
-        long getTotalHashtable = 0;
-
-        String name = "LRUMap";
-        String cache2Name = "";
-
-        try
-        {
-            LRUMap<String, String> cache = new LRUMap<>( tries );
-
-            for ( int j = 0; j < loops; j++ )
-            {
-                name = "JCS      ";
-                start = System.currentTimeMillis();
-                for ( int i = 0; i < tries; i++ )
-                {
-                    cache.put( "key:" + i, "data" + i );
-                }
-                end = System.currentTimeMillis();
-                time = end - start;
-                putTotalJCS += time;
-                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
-                System.out.println( name + " put time for " + tries + " = " + time + "; millis per = " + tPer );
-
-                start = System.currentTimeMillis();
-                for ( int i = 0; i < tries; i++ )
-                {
-                    cache.get( "key:" + i );
-                }
-                end = System.currentTimeMillis();
-                time = end - start;
-                getTotalJCS += time;
-                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
-                System.out.println( name + " get time for " + tries + " = " + time + "; millis per = " + tPer );
-
-                ///////////////////////////////////////////////////////////////
-                cache2Name = "LRUMap (commons)";
-                //or LRUMapJCS
-                Map<String, String> cache2 = new org.apache.commons.collections4.map.LRUMap<String, String>( tries );
-//                Map<String, String> cache2 = new ConcurrentLinkedHashMap.Builder<String, String>()
-//                        .maximumWeightedCapacity( tries )
-//                        .build();
-                //cache2Name = "Hashtable";
-                //Hashtable cache2 = new Hashtable();
-                start = System.currentTimeMillis();
-                for ( int i = 0; i < tries; i++ )
-                {
-                    cache2.put( "key:" + i, "data" + i );
-                }
-                end = System.currentTimeMillis();
-                time = end - start;
-                putTotalHashtable += time;
-                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
-                System.out.println( cache2Name + " put time for " + tries + " = " + time + "; millis per = " + tPer );
-
-                start = System.currentTimeMillis();
-                for ( int i = 0; i < tries; i++ )
-                {
-                    cache2.get( "key:" + i );
-                }
-                end = System.currentTimeMillis();
-                time = end - start;
-                getTotalHashtable += time;
-                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
-                System.out.println( cache2Name + " get time for " + tries + " = " + time + "; millis per = " + tPer );
-
-                System.out.println( "\n" );
-            }
-        }
-        catch ( Exception e )
-        {
-            e.printStackTrace( System.out );
-            System.out.println( e );
-        }
-
-        long putAvJCS = putTotalJCS / loops;
-        long getAvJCS = getTotalJCS / loops;
-        long putAvHashtable = putTotalHashtable / loops;
-        long getAvHashtable = getTotalHashtable / loops;
-
-        System.out.println( "Finished " + loops + " loops of " + tries + " gets and puts" );
-
-        System.out.println( "\n" );
-        System.out.println( "Put average for LRUMap       = " + putAvJCS );
-        System.out.println( "Put average for " + cache2Name + " = " + putAvHashtable );
-        ratioPut = Float.intBitsToFloat( (int) putAvJCS ) / Float.intBitsToFloat( (int) putAvHashtable );
-        System.out.println( name + " puts took " + ratioPut + " times the " + cache2Name + ", the goal is <" + targetPut
-            + "x" );
-
-        System.out.println( "\n" );
-        System.out.println( "Get average for LRUMap       = " + getAvJCS );
-        System.out.println( "Get average for " + cache2Name + " = " + getAvHashtable );
-        ratioGet = Float.intBitsToFloat( (int) getAvJCS ) / Float.intBitsToFloat( (int) getAvHashtable );
-        System.out.println( name + " gets took " + ratioGet + " times the " + cache2Name + ", the goal is <" + targetGet
-            + "x" );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/struct/LRUMapUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/struct/LRUMapUnitTest.java
deleted file mode 100644
index 3d33c95..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/struct/LRUMapUnitTest.java
+++ /dev/null
@@ -1,133 +0,0 @@
-package org.apache.commons.jcs.utils.struct;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-
-import junit.framework.TestCase;
-
-/**
- * Basic unit tests for the LRUMap
- *
- * @author Aaron Smuts
- *
- */
-public class LRUMapUnitTest
-    extends TestCase
-{
-
-    /**
-     * Put up to the size limit and then make sure they are all there.
-     *
-     */
-    public void testPutWithSizeLimit()
-    {
-        int size = 10;
-        Map<String, String> cache = new LRUMap<>( size );
-
-        for ( int i = 0; i < size; i++ )
-        {
-            cache.put( "key:" + i, "data:" + i );
-        }
-
-        for ( int i = 0; i < size; i++ )
-        {
-            String data = cache.get( "key:" + i );
-            assertEquals( "Data is wrong.", "data:" + i, data );
-        }
-    }
-
-    /**
-     * Put into the lru with no limit and then make sure they are all there.
-     *
-     */
-    public void testPutWithNoSizeLimit()
-    {
-        int size = 10;
-        Map<String, String> cache = new LRUMap<>( );
-
-        for ( int i = 0; i < size; i++ )
-        {
-            cache.put( "key:" + i, "data:" + i );
-        }
-
-        for ( int i = 0; i < size; i++ )
-        {
-            String data = cache.get( "key:" + i );
-            assertEquals( "Data is wrong.", "data:" + i, data );
-        }
-    }
-
-    /**
-     * Put and then remove.  Make sure the element is returned.
-     *
-     */
-    public void testPutAndRemove()
-    {
-        int size = 10;
-        Map<String, String> cache = new LRUMap<>( size );
-
-        cache.put( "key:" + 1, "data:" + 1 );
-        String data = cache.remove( "key:" + 1 );
-        assertEquals( "Data is wrong.", "data:" + 1, data );
-    }
-
-    /**
-     * Call remove on an empty map
-     *
-     */
-    public void testRemoveEmpty()
-    {
-        int size = 10;
-        Map<String, String> cache = new LRUMap<>( size );
-
-        Object returned = cache.remove( "key:" + 1 );
-        assertNull( "Shouldn't hvae anything.", returned );
-    }
-
-
-    /**
-     * Add items to the map and then test to see that they come back in the entry set.
-     *
-     */
-    public void testGetEntrySet()
-    {
-        int size = 10;
-        Map<String, String> cache = new LRUMap<>( size );
-
-        for ( int i = 0; i < size; i++ )
-        {
-            cache.put( "key:" + i, "data:" + i );
-        }
-
-        Set<Entry<String, String>> entries = cache.entrySet();
-        assertEquals( "Set contains the wrong number of items.", size, entries.size() );
-
-        // check minimal correctness
-        for (Entry<String, String> data : entries)
-        {
-            assertTrue( "Data is wrong.", data.getValue().indexOf( "data:") != -1  );
-        }
-    }
-
-
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/threadpool/ThreadPoolManagerUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/threadpool/ThreadPoolManagerUnitTest.java
deleted file mode 100644
index 3831954..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/threadpool/ThreadPoolManagerUnitTest.java
+++ /dev/null
@@ -1,87 +0,0 @@
-package org.apache.commons.jcs.utils.threadpool;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.Properties;
-import java.util.Set;
-import java.util.concurrent.ExecutorService;
-
-import org.apache.commons.jcs.utils.props.PropertyLoader;
-
-import junit.framework.TestCase;
-
-/**
- * Verify that the manager can create pools as intended by the default and
- * specified file names.
- *
- * @author asmuts
- */
-public class ThreadPoolManagerUnitTest
-    extends TestCase
-{
-
-    /**
-     * Make sure it can load a default cache.ccf file
-     */
-    public void testDefaultConfig()
-    {
-        Properties props = PropertyLoader.loadProperties( "thread_pool.properties" );
-        ThreadPoolManager.setProps( props );
-        ThreadPoolManager mgr = ThreadPoolManager.getInstance();
-        assertNotNull( mgr );
-
-        ExecutorService pool = mgr.getExecutorService( "test1" );
-        assertNotNull( pool );
-    }
-
-    /**
-     * Make sure it can load a certain configuration
-     */
-    public void testSpecialConfig()
-    {
-        Properties props = PropertyLoader.loadProperties( "thread_pool.properties" );
-        ThreadPoolManager.setProps( props );
-        ThreadPoolManager mgr = ThreadPoolManager.getInstance();
-        assertNotNull( mgr );
-
-        ExecutorService pool = mgr.getExecutorService( "aborttest" );
-        assertNotNull( pool );
-    }
-
-    /**
-     * Get a couple pools by name and then see if they are in the list.
-     *
-     */
-    public void testGetPoolNames()
-    {
-        ThreadPoolManager mgr = ThreadPoolManager.getInstance();
-        assertNotNull( mgr );
-
-        String poolName1 = "testGetPoolNames1";
-        mgr.getExecutorService( poolName1 );
-
-        String poolName2 = "testGetPoolNames2";
-        mgr.getExecutorService( poolName2 );
-
-        Set<String> names = mgr.getPoolNames();
-        assertTrue( "Should have name in list.", names.contains( poolName1 ) );
-        assertTrue( "Should have name in list.", names.contains( poolName2 ) );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/timing/SleepUtil.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/timing/SleepUtil.java
deleted file mode 100644
index e1c09cd..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/timing/SleepUtil.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package org.apache.commons.jcs.utils.timing;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * Utility methods to help deal with thread issues.
- */
-public class SleepUtil
-{
-    /**
-     * Sleep for a specified duration in milliseconds. This method is a
-     * platform-specific workaround for Windows due to its inability to resolve
-     * durations of time less than approximately 10 - 16 ms.
-     * <p>
-     * @param milliseconds the number of milliseconds to sleep
-     */
-    public static void sleepAtLeast( long milliseconds )
-    {
-        long endTime = System.currentTimeMillis() + milliseconds;
-
-        while ( System.currentTimeMillis() <= endTime )
-        {
-            try
-            {
-                Thread.sleep( milliseconds );
-            }
-            catch ( InterruptedException e )
-            {
-                // TODO - Do something here?
-            }
-        }
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/zip/CompressionUtilUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/zip/CompressionUtilUnitTest.java
deleted file mode 100644
index 1c390f7..0000000
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs/utils/zip/CompressionUtilUnitTest.java
+++ /dev/null
@@ -1,97 +0,0 @@
-package org.apache.commons.jcs.utils.zip;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import junit.framework.TestCase;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.util.zip.GZIPOutputStream;
-
-/** Unit tests for the compression util */
-public class CompressionUtilUnitTest
-    extends TestCase
-{
-    /** Test method for decompressByteArray. */
-    public final void testDecompressByteArray_failure()
-    {
-        try
-        {
-            // DO WORK
-            CompressionUtil.decompressByteArray( null );
-
-            // VERIFY
-            fail( "excepted an IllegalArgumentException" );
-        }
-        catch ( IllegalArgumentException exception )
-        {
-            // expected
-            return;
-        }
-    }
-
-    /**
-     * Test method for decompressByteArray.
-     * <p>
-     * @throws IOException
-     */
-    public final void testCompressDecompressByteArray_success()
-        throws IOException
-    {
-        // SETUP
-        String text = "This is some text to compress, not a lot, just a bit ";
-
-        // DO WORK
-        byte[] compressedText = CompressionUtil.compressByteArray( text.getBytes() );
-        byte[] output = CompressionUtil.decompressByteArray( compressedText );
-
-        // VERIFY
-        String result = new String( output );
-        assertNotNull( "decompressed output stream shouldn't have been null ", output );
-        assertEquals( text, result );
-    }
-
-    /**
-     * Test method for decompressByteArray.
-     * <p>
-     * @throws IOException
-     */
-    public final void testCompressDecompressGzipByteArray_success()
-        throws IOException
-    {
-        // SETUP
-        String text = " This is some text to compress, not a lot, just a bit ";
-
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        GZIPOutputStream os = new GZIPOutputStream( baos );
-
-        os.write( text.getBytes() );
-        os.flush();
-        os.close();
-
-        // DO WORK
-        byte[] output = CompressionUtil.decompressGzipByteArray( baos.toByteArray() );
-
-        // VERIFY
-        String result = new String( output );
-        assertNotNull( "decompressed output stream shouldn't have been null ", output );
-        assertEquals( text, result );
-    }
-}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/ConcurrentRemovalLoadTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/ConcurrentRemovalLoadTest.java
new file mode 100644
index 0000000..5ff05c0
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/ConcurrentRemovalLoadTest.java
@@ -0,0 +1,139 @@
+package org.apache.commons.jcs3;
+
+import org.apache.commons.jcs3.JCS;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.extensions.ActiveTestSuite;
+import junit.framework.Test;
+import junit.framework.TestCase;
+
+/**
+ * Test which exercises the hierarchical removal when the cache is active.
+ */
+public class ConcurrentRemovalLoadTest
+    extends TestCase
+{
+    /**
+     * A unit test suite for JUnit. This verifies that we can remove hierarchically while the region
+     * is active.
+     * @return The test suite
+     */
+    public static Test suite()
+    {
+        ActiveTestSuite suite = new ActiveTestSuite();
+
+        suite.addTest( new RemovalTestUtil( "testRemoveCache1" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                runTestPutThenRemoveCategorical( 0, 200 );
+            }
+        } );
+
+        suite.addTest( new RemovalTestUtil( "testPutCache1" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                runPutInRange( 300, 400 );
+            }
+        } );
+
+        suite.addTest( new RemovalTestUtil( "testPutCache2" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                runPutInRange( 401, 600 );
+            }
+        } );
+
+        // stomp on previous put
+        suite.addTest( new RemovalTestUtil( "testPutCache3" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                runPutInRange( 401, 600 );
+            }
+        } );
+
+        suite.addTest( new RemovalTestUtil( "testRemoveCache1" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                runTestPutThenRemoveCategorical( 601, 700 );
+            }
+        } );
+
+        suite.addTest( new RemovalTestUtil( "testRemoveCache1" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                runTestPutThenRemoveCategorical( 701, 800 );
+            }
+        } );
+
+        suite.addTest( new RemovalTestUtil( "testRemoveCache1" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                runTestPutThenRemoveCategorical( 901, 1000 );
+            }
+        } );
+
+        suite.addTest( new RemovalTestUtil( "testPutCache2" )
+        {
+            // verify that there are no errors with concurrent gets.
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                runGetInRange( 0, 1000, false );
+            }
+        } );
+        return suite;
+    }
+
+    /**
+     * Test setup
+     * <p>
+     * @throws Exception
+     */
+    @Override
+    public void setUp()
+        throws Exception
+    {
+        JCS.setConfigFilename( "/TestRemoval.ccf" );
+        JCS.getInstance( "testCache1" );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/JCSCacheElementRetrievalUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/JCSCacheElementRetrievalUnitTest.java
new file mode 100644
index 0000000..75f1c5c
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/JCSCacheElementRetrievalUnitTest.java
@@ -0,0 +1,54 @@
+package org.apache.commons.jcs3;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/**
+ * @author Aaron Smuts
+ *
+ */
+public class JCSCacheElementRetrievalUnitTest
+    extends TestCase
+{
+    /**
+     *
+     * @throws Exception
+     */
+    public void testSimpleElementRetrieval()
+        throws Exception
+    {
+        CacheAccess<String, String> jcs = JCS.getInstance( "testCache1" );
+
+        jcs.put( "test_key", "test_data" );
+
+        long now = System.currentTimeMillis();
+        ICacheElement<String, String> elem = jcs.getCacheElement( "test_key" );
+        assertEquals( "Name wasn't right", "testCache1", elem.getCacheName() );
+
+        long diff = now - elem.getElementAttributes().getCreateTime();
+        assertTrue( "Create time should have been at or after the call", diff >= 0 );
+
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/JCSConcurrentCacheAccessUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/JCSConcurrentCacheAccessUnitTest.java
new file mode 100644
index 0000000..0777f0c
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/JCSConcurrentCacheAccessUnitTest.java
@@ -0,0 +1,183 @@
+package org.apache.commons.jcs3;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.GroupCacheAccess;
+import org.apache.commons.jcs3.access.exception.CacheException;
+
+import junit.framework.TestCase;
+
+/**
+ * Test Case for JCS-73, modeled after the Groovy code by Alexander Kleymenov
+ *
+ * @author Thomas Vandahl
+ *
+ */
+public class JCSConcurrentCacheAccessUnitTest extends TestCase
+{
+    private final static int THREADS = 20;
+    private final static int LOOPS = 10000;
+
+    /**
+     * the cache instance
+     */
+    protected GroupCacheAccess<Integer, String> cache;
+
+    /**
+     * the group name
+     */
+    protected String group = "group";
+
+    /**
+     * the error count
+     */
+    protected AtomicInteger errcount;
+
+    /**
+     * Collect all value mismatches
+     */
+    protected List<String> valueMismatchList;
+
+    @Override
+	protected void setUp() throws Exception
+	{
+        super.setUp();
+        JCS.setConfigFilename( "/TestJCS-73.ccf" );
+        cache = JCS.getGroupCacheInstance( "cache" );
+        errcount = new AtomicInteger(0);
+        valueMismatchList = new CopyOnWriteArrayList<>();
+	}
+
+    @Override
+    protected void tearDown()
+        throws Exception
+    {
+        super.tearDown();
+        cache.clear();
+        cache.dispose();
+    }
+
+    /**
+     * Worker thread
+     */
+    protected class Worker extends Thread
+    {
+    	@Override
+		public void run()
+		{
+			String name = getName();
+
+			for (int idx = 0; idx < LOOPS; idx++)
+			{
+				if (idx > 0)
+				{
+					// get previously stored value
+		            String res = cache.getFromGroup(Integer.valueOf(idx-1), group);
+
+		            if (res == null)
+		            {
+		                // null value got inspite of the fact it was placed in cache!
+		                System.out.println("ERROR: for " + idx + " in " + name);
+		                errcount.incrementAndGet();
+
+		                // try to get the value again:
+		                int n = 5;
+		                while (n-- > 0)
+		                {
+		                    res = cache.getFromGroup(Integer.valueOf(idx-1), group);
+		                    if (res != null)
+		                    {
+		                        // the value finally appeared in cache
+		                    	System.out.println("ERROR FIXED for " + idx + ": " + res + " " + name);
+		                    	errcount.decrementAndGet();
+		                        break;
+		                    }
+
+		                    System.out.println("ERROR STILL PERSISTS for " + idx + " in " + name);
+		                    try
+		                    {
+								Thread.sleep(1000);
+							}
+		                    catch (InterruptedException e)
+							{
+								// continue
+							}
+		                }
+		            }
+
+		            if (!String.valueOf(idx-1).equals(res))
+		            {
+		                valueMismatchList.add(String.format("Values do not match: %s - %s", String.valueOf(idx-1), res));
+		            }
+				}
+
+				 // put value in the cache
+		        try
+		        {
+					cache.putInGroup(Integer.valueOf(idx), group, String.valueOf(idx));
+				}
+		        catch (CacheException e)
+		        {
+		        	// continue
+				}
+
+//		        if ((idx % 1000) == 0)
+//		        {
+//		        	System.out.println(name + " " + idx);
+//		        }
+			}
+
+		}
+    }
+
+	/**
+     *
+     * @throws Exception
+     */
+    public void testConcurrentAccess()
+        throws Exception
+    {
+    	Worker[] worker = new Worker[THREADS];
+
+        for (int i = 0; i < THREADS; i++)
+        {
+        	worker[i] = new Worker();
+        	worker[i].start();
+        }
+
+        for (int i = 0; i < THREADS; i++)
+        {
+        	worker[i].join();
+        }
+
+        assertEquals("Error count should be 0",  0, errcount.intValue());
+        for (String msg : valueMismatchList)
+        {
+            System.out.println(msg);
+        }
+        assertEquals("Value mismatch count should be 0",  0, valueMismatchList.size());
+    }
+
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/JCSLightLoadUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/JCSLightLoadUnitTest.java
new file mode 100644
index 0000000..be5a06c
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/JCSLightLoadUnitTest.java
@@ -0,0 +1,75 @@
+package org.apache.commons.jcs3;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+
+import junit.framework.TestCase;
+
+/**
+ * Runs a few thousand queries.
+ */
+public class JCSLightLoadUnitTest
+    extends TestCase
+{
+    /** number to use for the test */
+    private static int items = 20000;
+
+    /**
+     * Test setup
+     * @throws Exception
+     */
+    @Override
+    public void setUp()
+        throws Exception
+    {
+        JCS.setConfigFilename( "/TestSimpleLoad.ccf" );
+        JCS.getInstance( "testCache1" );
+    }
+
+    /**
+     * A unit test for JUnit
+     * @throws Exception Description of the Exception
+     */
+    public void testSimpleLoad()
+        throws Exception
+    {
+        CacheAccess<String, String> jcs = JCS.getInstance( "testCache1" );
+        //        ICompositeCacheAttributes cattr = jcs.getCacheAttributes();
+        //        cattr.setMaxObjects( 20002 );
+        //        jcs.setCacheAttributes( cattr );
+
+        for ( int i = 1; i <= items; i++ )
+        {
+            jcs.put( i + ":key", "data" + i );
+        }
+
+        for ( int i = items; i > 0; i-- )
+        {
+            String res = jcs.get( i + ":key" );
+            assertNotNull( "[" + i + ":key] should not be null", res );
+        }
+
+        // test removal
+        jcs.remove( "300:key" );
+        assertNull( jcs.get( "300:key" ) );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/JCSRemovalSimpleConcurrentTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/JCSRemovalSimpleConcurrentTest.java
new file mode 100644
index 0000000..eab03e6
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/JCSRemovalSimpleConcurrentTest.java
@@ -0,0 +1,194 @@
+package org.apache.commons.jcs3;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/**
+ * Verify that basic removal functionality works.
+ */
+public class JCSRemovalSimpleConcurrentTest
+    extends TestCase
+{
+    /**
+     * @param testName
+     */
+    public JCSRemovalSimpleConcurrentTest( String testName )
+    {
+        super( testName );
+    }
+
+    /**
+     * Test setup
+     * <p>
+     * @throws Exception
+     */
+    @Override
+    public void setUp()
+        throws Exception
+    {
+        JCS.setConfigFilename( "/TestRemoval.ccf" );
+        JCS.getInstance( "testCache1" );
+    }
+
+    /**
+     * Main method passes this test to the text test runner.
+     * <p>
+     * @param args
+     */
+    public static void main( String args[] )
+    {
+        String[] testCaseName = { JCSRemovalSimpleConcurrentTest.class.getName() };
+        junit.textui.TestRunner.main( testCaseName );
+    }
+
+    /**
+     * Verify that 2 level deep hierchical removal works.
+     * <p>
+     * @throws Exception
+     */
+    public void testTwoDeepRemoval()
+        throws Exception
+    {
+        int count = 500;
+        CacheAccess<String, String> jcs = JCS.getInstance( "testCache1" );
+
+        for ( int i = 0; i <= count; i++ )
+        {
+            jcs.put( "key:" + i + ":anotherpart", "data" + i );
+        }
+
+        for ( int i = count; i >= 0; i-- )
+        {
+            String res = jcs.get( "key:" + i + ":anotherpart" );
+            assertNotNull( "[key:" + i + ":anotherpart] should not be null, " + jcs.getStats(), res );
+        }
+
+        for ( int i = 0; i <= count; i++ )
+        {
+            jcs.remove( "key:" + i + ":" );
+            assertNull( jcs.getStats(), jcs.get( "key:" + i + ":anotherpart" ) );
+        }
+
+    }
+
+    /**
+     * Verify that 1 level deep hierchical removal works.
+     *
+     * @throws Exception
+     */
+    public void testSingleDepthRemoval()
+        throws Exception
+    {
+
+        int count = 500;
+        CacheAccess<String, String> jcs = JCS.getInstance( "testCache1" );
+
+        for ( int i = 0; i <= count; i++ )
+        {
+            jcs.put( i + ":key", "data" + i );
+        }
+
+        for ( int i = count; i >= 0; i-- )
+        {
+            String res = jcs.get( i + ":key" );
+            assertNotNull( "[" + i + ":key] should not be null", res );
+        }
+
+        for ( int i = 0; i <= count; i++ )
+        {
+            jcs.remove( i + ":" );
+            assertNull( jcs.get( i + ":key" ) );
+        }
+    }
+
+    /**
+     * Verify that clear removes everyting as it should.
+     * <p>
+     * @throws Exception
+     */
+    public void testClear()
+        throws Exception
+    {
+
+        int count = 500;
+        CacheAccess<String, String> jcs = JCS.getInstance( "testCache1" );
+
+        for ( int i = 0; i <= count; i++ )
+        {
+            jcs.put( i + ":key", "data" + i );
+        }
+
+        for ( int i = count; i >= 0; i-- )
+        {
+            String res = jcs.get( i + ":key" );
+            assertNotNull( "[" + i + ":key] should not be null", res );
+        }
+        jcs.clear();
+
+        for ( int i = count; i >= 0; i-- )
+        {
+            String res = jcs.get( i + ":key" );
+            if ( res != null )
+            {
+                assertNull( "[" + i + ":key] should be null after remvoeall" + jcs.getStats(), res );
+            }
+        }
+    }
+
+    /**
+     * Verify that we can clear repeatedly without error.
+     *
+     * @throws Exception
+     */
+    public void testClearRepeatedlyWithoutError()
+        throws Exception
+    {
+        int count = 500;
+        CacheAccess<String, String> jcs = JCS.getInstance( "testCache1" );
+
+        jcs.clear();
+
+        for ( int i = 0; i <= count; i++ )
+        {
+            jcs.put( i + ":key", "data" + i );
+        }
+
+        for ( int i = count; i >= 0; i-- )
+        {
+            String res = jcs.get( i + ":key" );
+            assertNotNull( "[" + i + ":key] should not be null", res );
+        }
+
+        for ( int i = count; i >= 0; i-- )
+        {
+            jcs.put( i + ":key", "data" + i );
+            jcs.clear();
+            String res = jcs.get( i + ":key" );
+            if ( res != null )
+            {
+                assertNull( "[" + i + ":key] should be null after remvoeall" + jcs.getStats(), res );
+            }
+        }
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/JCSThrashTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/JCSThrashTest.java
new file mode 100644
index 0000000..7a7988c
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/JCSThrashTest.java
@@ -0,0 +1,307 @@
+package org.apache.commons.jcs3;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.engine.stats.behavior.IStatElement;
+import org.apache.commons.jcs3.engine.stats.behavior.IStats;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+import junit.framework.TestCase;
+
+/**
+ * This is based on a test that was posted to the user's list:
+ * <p>
+ * http://www.opensubscriber.com/message/jcs-users@jakarta.apache.org/2435965.html
+ */
+public class JCSThrashTest
+    extends TestCase
+{
+    /** The logger. */
+    private static final Log LOG = LogManager.getLog( JCSThrashTest.class.getName() );
+
+    /**
+     * the cache instance
+     */
+    protected CacheAccess<String, Serializable> jcs;
+
+    /**
+     * Sets up the test
+     * @throws Exception
+     */
+    @Override
+    protected void setUp()
+        throws Exception
+    {
+        super.setUp();
+        JCS.setConfigFilename( "/TestThrash.ccf" );
+        jcs = JCS.getInstance( "testcache" );
+    }
+
+    /**
+     * @throws Exception
+     */
+    @Override
+    protected void tearDown()
+        throws Exception
+    {
+        super.tearDown();
+        jcs.clear();
+        jcs.dispose();
+    }
+
+    /**
+     * Tests adding an entry.
+     * @throws Exception
+     */
+    public void testPut()
+        throws Exception
+    {
+        final String value = "value";
+        final String key = "key";
+
+        // Make sure the element is not found
+        assertEquals( 0, getListSize() );
+
+        assertNull( jcs.get( key ) );
+
+        jcs.put( key, value );
+
+        // Get the element
+        LOG.info( "jcs.getStats(): " + jcs.getStatistics() );
+        assertEquals( 1, getListSize() );
+        assertNotNull( jcs.get( key ) );
+        assertEquals( value, jcs.get( key ) );
+    }
+
+    /**
+     * Test elements can be removed from the store
+     * @throws Exception
+     */
+    public void testRemove()
+        throws Exception
+    {
+        jcs.put( "key1", "value1" );
+        assertEquals( 1, getListSize() );
+
+        jcs.remove( "key1" );
+        assertEquals( 0, getListSize() );
+
+        jcs.put( "key2", "value2" );
+        jcs.put( "key3", "value3" );
+        assertEquals( 2, getListSize() );
+
+        jcs.remove( "key2" );
+        assertEquals( 1, getListSize() );
+
+        // Try to remove an object that is not there in the store
+        jcs.remove( "key4" );
+        assertEquals( 1, getListSize() );
+    }
+
+    /**
+     * This does a bunch of work and then verifies that the memory has not grown by much. Most of
+     * the time the amount of memory used after the test is less.
+     * @throws Exception
+     */
+    public void testForMemoryLeaks()
+        throws Exception
+    {
+        long differenceMemoryCache = thrashCache();
+        LOG.info( "Memory Difference is: " + differenceMemoryCache );
+        assertTrue( differenceMemoryCache < 500000 );
+
+        //LOG.info( "Memory Used is: " + measureMemoryUse() );
+    }
+
+    /**
+     * @return time
+     * @throws Exception
+     */
+    protected long thrashCache()
+        throws Exception
+    {
+        long startingSize = measureMemoryUse();
+        LOG.info( "Memory Used is: " + startingSize );
+
+        final String value = "value";
+        final String key = "key";
+
+        // Add the entry
+        jcs.put( key, value );
+
+        // Create 15 threads that read the keys;
+        final List<Executable> executables = new ArrayList<>();
+        for ( int i = 0; i < 15; i++ )
+        {
+            final JCSThrashTest.Executable executable = () ->
+            {
+                for ( int j = 0; j < 500; j++ )
+                {
+                    final String keyj = "key" + j;
+                    jcs.get( keyj );
+                }
+                jcs.get( "key" );
+            };
+            executables.add( executable );
+        }
+
+        // Create 15 threads that are insert 500 keys with large byte[] as
+        // values
+        for ( int i = 0; i < 15; i++ )
+        {
+            final JCSThrashTest.Executable executable = () ->
+            {
+
+                // Add a bunch of entries
+                for ( int j = 0; j < 500; j++ )
+                {
+                    // Use a random length value
+                    final String keyj = "key" + j;
+                    byte[] valuej = new byte[10000];
+                    jcs.put( keyj, valuej );
+                }
+            };
+            executables.add( executable );
+        }
+
+        runThreads( executables );
+        jcs.clear();
+
+        long finishingSize = measureMemoryUse();
+        LOG.info( "Memory Used is: " + finishingSize );
+        return finishingSize - startingSize;
+    }
+
+    /**
+     * Runs a set of threads, for a fixed amount of time.
+     * <p>
+     * @param executables
+     * @throws Exception
+     */
+    protected void runThreads( final List<Executable> executables )
+        throws Exception
+    {
+
+        final long endTime = System.currentTimeMillis() + 10000;
+        final Throwable[] errors = new Throwable[1];
+
+        // Spin up the threads
+        final Thread[] threads = new Thread[executables.size()];
+        for ( int i = 0; i < threads.length; i++ )
+        {
+            final JCSThrashTest.Executable executable = executables.get( i );
+            threads[i] = new Thread()
+            {
+                @Override
+                public void run()
+                {
+                    try
+                    {
+                        // Run the thread until the given end time
+                        while ( System.currentTimeMillis() < endTime )
+                        {
+                            executable.execute();
+                        }
+                    }
+                    catch ( Throwable t )
+                    {
+                        // Hang on to any errors
+                        errors[0] = t;
+                    }
+                }
+            };
+            threads[i].start();
+        }
+
+        // Wait for the threads to finish
+        for ( int i = 0; i < threads.length; i++ )
+        {
+            threads[i].join();
+        }
+
+        // Throw any error that happened
+        if ( errors[0] != null )
+        {
+            throw new Exception( "Test thread failed.", errors[0] );
+        }
+    }
+
+    /**
+     * Measure memory used by the VM.
+     * <p>
+     * @return bytes
+     * @throws InterruptedException
+     */
+    protected long measureMemoryUse()
+        throws InterruptedException
+    {
+        System.gc();
+        Thread.sleep( 3000 );
+        System.gc();
+        return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+    }
+
+    /**
+     * A runnable, that can throw an exception.
+     */
+    protected interface Executable
+    {
+        /**
+         * Executes this object.
+         * @throws Exception
+         */
+        void execute()
+            throws Exception;
+    }
+
+    /**
+     * @return size
+     */
+    private int getListSize()
+    {
+        final String listSize = "List Size";
+        final String lruMemoryCache = "LRU Memory Cache";
+        String result = "0";
+        List<IStats> istats = jcs.getStatistics().getAuxiliaryCacheStats();
+        for ( IStats istat : istats )
+        {
+            List<IStatElement<?>> statElements = istat.getStatElements();
+            if ( lruMemoryCache.equals( istat.getTypeName() ) )
+            {
+                for ( IStatElement<?> statElement : statElements )
+                {
+                    if ( listSize.equals( statElement.getName() ) )
+                    {
+                        result = statElement.getData().toString();
+                        break;
+                    }
+                }
+            }
+        }
+        return Integer.parseInt( result );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/JCSUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/JCSUnitTest.java
new file mode 100644
index 0000000..b7af360
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/JCSUnitTest.java
@@ -0,0 +1,90 @@
+package org.apache.commons.jcs3;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Random;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+
+import junit.framework.TestCase;
+
+/**
+ * Simple test for the JCS class.
+ */
+public class JCSUnitTest
+    extends TestCase
+{
+    /** A random for key generation. */
+    Random random = new Random();
+
+    /**
+     * @throws Exception
+     */
+    public void testJCS()
+        throws Exception
+    {
+        CacheAccess<String, LinkedList<HashMap<String, String>>> jcs = JCS.getInstance( "testCache1" );
+
+        LinkedList<HashMap<String, String>> list = buildList();
+
+        jcs.put( "some:key", list );
+
+        assertEquals( list, jcs.get( "some:key" ) );
+    }
+
+    /**
+     * @return builds a list
+     */
+    private LinkedList<HashMap<String, String>> buildList()
+    {
+        LinkedList<HashMap<String, String>> list = new LinkedList<>();
+
+        for ( int i = 0; i < 100; i++ )
+        {
+            list.add( buildMap() );
+        }
+
+        return list;
+    }
+
+    /**
+     * @return a map
+     */
+    private HashMap<String, String> buildMap()
+    {
+        HashMap<String, String> map = new HashMap<>();
+
+        byte[] keyBytes = new byte[32];
+        byte[] valBytes = new byte[128];
+
+        for ( int i = 0; i < 10; i++ )
+        {
+            random.nextBytes( keyBytes );
+            random.nextBytes( valBytes );
+
+            map.put( new String( keyBytes ), new String( valBytes ) );
+        }
+
+        return map;
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/JCSvsHashtablePerformanceTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/JCSvsHashtablePerformanceTest.java
new file mode 100644
index 0000000..ef207d2
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/JCSvsHashtablePerformanceTest.java
@@ -0,0 +1,181 @@
+package org.apache.commons.jcs3;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Hashtable;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+import junit.framework.TestCase;
+
+/**
+ * This test ensures that basic memory operations are with a specified order of magnitude of the
+ * java.util.Hashtable.
+ * <p>
+ * Currently JCS is under 2x a hashtable for gets, and under 1.2x for puts.
+ */
+public class JCSvsHashtablePerformanceTest
+    extends TestCase
+{
+    /** jcs / hashtable */
+    float ratioPut = 0;
+
+    /** jcs / hashtable */
+    float ratioGet = 0;
+
+    /** ration goal */
+    float target = 3.50f;
+
+    /** Times to run the test */
+    int loops = 20;
+
+    /** how many puts and gets to run */
+    int tries = 50000;
+
+    /**
+     * A unit test for JUnit
+     * @throws Exception Description of the Exception
+     */
+    public void testSimpleLoad()
+        throws Exception
+    {
+        Log log1 = LogManager.getLog( LRUMemoryCache.class );
+        if ( log1.isDebugEnabled() )
+        {
+            System.out.println( "The log level must be at info or above for the a performance test." );
+            return;
+        }
+        Log log2 = LogManager.getLog( JCS.class );
+        if ( log2.isDebugEnabled() )
+        {
+            System.out.println( "The log level must be at info or above for the a performance test." );
+            return;
+        }
+        doWork();
+        assertTrue( this.ratioPut < target );
+        assertTrue( this.ratioGet < target );
+    }
+
+    /**
+     *
+     */
+    public void doWork()
+    {
+        long start = 0;
+        long end = 0;
+        long time = 0;
+        float tPer = 0;
+
+        long putTotalJCS = 0;
+        long getTotalJCS = 0;
+        long putTotalHashtable = 0;
+        long getTotalHashtable = 0;
+
+        try
+        {
+
+            JCS.setConfigFilename( "/TestJCSvHashtablePerf.ccf" );
+            CacheAccess<String, String> cache = JCS.getInstance( "testCache1" );
+
+            for ( int j = 0; j < loops; j++ )
+            {
+
+                String name = "JCS      ";
+                start = System.currentTimeMillis();
+                for ( int i = 0; i < tries; i++ )
+                {
+                    cache.put( "key:" + i, "data" + i );
+                }
+                end = System.currentTimeMillis();
+                time = end - start;
+                putTotalJCS += time;
+                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
+                System.out.println( name + " put time for " + tries + " = " + time + "; millis per = " + tPer );
+
+                start = System.currentTimeMillis();
+                for ( int i = 0; i < tries; i++ )
+                {
+                    cache.get( "key:" + i );
+                }
+                end = System.currentTimeMillis();
+                time = end - start;
+                getTotalJCS += time;
+                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
+                System.out.println( name + " get time for " + tries + " = " + time + "; millis per = " + tPer );
+
+                // /////////////////////////////////////////////////////////////
+                name = "Hashtable";
+                Hashtable<String, String> cache2 = new Hashtable<>();
+                start = System.currentTimeMillis();
+                for ( int i = 0; i < tries; i++ )
+                {
+                    cache2.put( "key:" + i, "data" + i );
+                }
+                end = System.currentTimeMillis();
+                time = end - start;
+                putTotalHashtable += time;
+                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
+                System.out.println( name + " put time for " + tries + " = " + time + "; millis per = " + tPer );
+
+                start = System.currentTimeMillis();
+                for ( int i = 0; i < tries; i++ )
+                {
+                    cache2.get( "key:" + i );
+                }
+                end = System.currentTimeMillis();
+                time = end - start;
+                getTotalHashtable += time;
+                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
+                System.out.println( name + " get time for " + tries + " = " + time + "; millis per = " + tPer );
+
+                System.out.println( "\n" );
+            }
+
+        }
+        catch ( Exception e )
+        {
+            e.printStackTrace( System.out );
+            System.out.println( e );
+        }
+
+        long putAvJCS = putTotalJCS / loops;
+        long getAvJCS = getTotalJCS / loops;
+        long putAvHashtable = putTotalHashtable / loops;
+        long getAvHashtable = getTotalHashtable / loops;
+
+        System.out.println( "Finished " + loops + " loops of " + tries + " gets and puts" );
+
+        System.out.println( "\n" );
+        System.out.println( "Put average for JCS       = " + putAvJCS );
+        System.out.println( "Put average for Hashtable = " + putAvHashtable );
+        ratioPut = Float.intBitsToFloat( (int) putAvJCS ) / Float.intBitsToFloat( (int) putAvHashtable );
+        System.out.println( "JCS puts took " + ratioPut + " times the Hashtable, the goal is <" + target + "x" );
+
+        System.out.println( "\n" );
+        System.out.println( "Get average for JCS       = " + getAvJCS );
+        System.out.println( "Get average for Hashtable = " + getAvHashtable );
+        ratioGet = Float.intBitsToFloat( (int) getAvJCS ) / Float.intBitsToFloat( (int) getAvHashtable );
+        System.out.println( "JCS gets took " + ratioGet + " times the Hashtable, the goal is <" + target + "x" );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/RemovalTestUtil.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/RemovalTestUtil.java
new file mode 100644
index 0000000..1d363e3
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/RemovalTestUtil.java
@@ -0,0 +1,131 @@
+package org.apache.commons.jcs3;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/**
+ * Simple methods to be run by active test suites that test removal.
+ *
+ */
+public class RemovalTestUtil
+    extends TestCase
+{
+    /**
+     * Constructor for the TestSimpleLoad object
+     *
+     * @param testName
+     *            Description of the Parameter
+     */
+    public RemovalTestUtil( String testName )
+    {
+        super( testName );
+    }
+
+    /**
+     * Adds elements in the range specified and then removes them using the
+     * categorical or substring removal method.
+     *
+     * @param start
+     * @param end
+     *
+     * @throws Exception
+     *                Description of the Exception
+     */
+    public void runTestPutThenRemoveCategorical( int start, int end )
+        throws Exception
+    {
+        CacheAccess<String, String> jcs = JCS.getInstance( "testCache1" );
+
+        for ( int i = start; i <= end; i++ )
+        {
+            jcs.put( i + ":key", "data" + i );
+        }
+
+        for ( int i = end; i >= start; i-- )
+        {
+            String res = jcs.get( i + ":key" );
+            assertNotNull( "[" + i + ":key] should not be null", res );
+        }
+
+        for ( int i = start; i <= end; i++ )
+        {
+            jcs.remove( i + ":" );
+            assertNull( jcs.get( i + ":key" ) );
+        }
+    }
+
+    /**
+     * Put items in the cache in this key range. Can be used to verify that
+     * concurrent operations are not effected by things like hierchical removal.
+     *
+     * @param start
+     *            int
+     * @param end
+     *            int
+     * @throws Exception
+     */
+    public void runPutInRange( int start, int end )
+        throws Exception
+    {
+        CacheAccess<String, String> jcs = JCS.getInstance( "testCache1" );
+
+        for ( int i = start; i <= end; i++ )
+        {
+            jcs.put( i + ":key", "data" + i );
+        }
+
+        for ( int i = end; i >= start; i-- )
+        {
+            String res = jcs.get( i + ":key" );
+            assertNotNull( "[" + i + ":key] should not be null", res );
+        }
+    }
+
+    /**
+     * Just get from start to end.
+     *
+     * @param start
+     *            int
+     * @param end
+     *            int
+     * @param check
+     *            boolean -- check to see if the items are in the cache.
+     * @throws Exception
+     */
+    public void runGetInRange( int start, int end, boolean check )
+        throws Exception
+    {
+        CacheAccess<String, String> jcs = JCS.getInstance( "testCache1" );
+
+        // don't care if they are found
+        for ( int i = end; i >= start; i-- )
+        {
+            String res = jcs.get( i + ":key" );
+            if ( check )
+            {
+                assertNotNull( "[" + i + ":key] should not be null", res );
+            }
+        }
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/TestLogConfigurationUtil.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/TestLogConfigurationUtil.java
new file mode 100644
index 0000000..e7ebce2
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/TestLogConfigurationUtil.java
@@ -0,0 +1,85 @@
+package org.apache.commons.jcs3;
+
+import java.io.StringWriter;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+
+import org.apache.commons.jcs3.log.LogManager;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/** Utility for testing log messages. */
+public class TestLogConfigurationUtil
+{
+    /**
+     * Configures a logger for the given name. This allows us to check the log output.
+     * <p>
+     * @param stringWriter string writer
+     * @param loggerName logger name
+     */
+    public static void configureLogger( StringWriter stringWriter, String loggerName )
+    {
+        LogManager.setLogSystem("jul");
+        java.util.logging.LogManager.getLogManager().reset();
+        Logger rootLogger = java.util.logging.LogManager.getLogManager().getLogger("");
+
+        rootLogger.addHandler(new MockLogHandler(stringWriter));
+        rootLogger.setLevel(Level.FINE);
+    }
+
+    private static class MockLogHandler extends Handler
+    {
+        private final StringWriter writer;
+
+        public MockLogHandler(StringWriter writer)
+        {
+            super();
+            this.writer = writer;
+        }
+
+        @Override
+        public void publish(LogRecord record)
+        {
+            StringBuilder sb = new StringBuilder();
+            sb.append(record.getMillis())
+              .append(" - ")
+              .append(record.getSourceClassName())
+              .append("#")
+              .append(record.getSourceMethodName())
+              .append(" - ")
+              .append(record.getMessage())
+              .append('\n');
+            writer.append(sb.toString());
+        }
+
+        @Override
+        public void flush()
+        {
+            writer.flush();
+        }
+
+        @Override
+        public void close() throws SecurityException
+        {
+        }
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/TestTCPLateralCache.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/TestTCPLateralCache.java
new file mode 100644
index 0000000..2418ab4
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/TestTCPLateralCache.java
@@ -0,0 +1,146 @@
+package org.apache.commons.jcs3;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.extensions.ActiveTestSuite;
+import junit.framework.Test;
+import junit.framework.TestCase;
+
+/**
+ * Test which exercises the indexed disk cache. This one uses three different
+ * regions for thre threads.
+ *
+ * @version $Id$
+ */
+public class TestTCPLateralCache
+    extends TestCase
+{
+    /**
+     * Number of items to cache, twice the configured maxObjects for the memory
+     * cache regions.
+     */
+    private static int items = 200;
+
+    /**
+     * Constructor for the TestTCPLateralCache object.
+     *
+     * @param testName
+     */
+    public TestTCPLateralCache( String testName )
+    {
+        super( testName );
+    }
+
+    /**
+     * A unit test suite for JUnit
+     *
+     * @return The test suite
+     */
+    public static Test suite()
+    {
+        ActiveTestSuite suite = new ActiveTestSuite();
+
+        suite.addTest( new TestTCPLateralCache( "testTcpRegion1_no_receiver" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "testTcpRegion1" );
+            }
+        } );
+
+        //        suite.addTest( new TestTCPLateralCache( "testIndexedDiskCache2" )
+        //        {
+        //            public void runTest() throws Exception
+        //            {
+        //                this.runTestForRegion( "indexedRegion2" );
+        //            }
+        //        } );
+        //
+        //        suite.addTest( new TestTCPLateralCache( "testIndexedDiskCache3" )
+        //        {
+        //            public void runTest() throws Exception
+        //            {
+        //                this.runTestForRegion( "indexedRegion3" );
+        //            }
+        //        } );
+
+        return suite;
+    }
+
+    /**
+     * Test setup
+     */
+    @Override
+    public void setUp()
+    {
+        JCS.setConfigFilename( "/TestTCPLateralCache.ccf" );
+    }
+
+    /**
+     * Adds items to cache, gets them, and removes them. The item count is more
+     * than the size of the memory cache, so items should spool to disk.
+     *
+     * @param region
+     *            Name of the region to access
+     *
+     * @throws Exception
+     *                If an error occurs
+     */
+    public void runTestForRegion( String region )
+        throws Exception
+    {
+        CacheAccess<String, String> jcs = JCS.getInstance( region );
+
+        // Add items to cache
+
+        for ( int i = 0; i <= items; i++ )
+        {
+            jcs.put( i + ":key", region + " data " + i );
+        }
+
+        // Test that all items are in cache
+
+        for ( int i = 0; i <= items; i++ )
+        {
+            String value = jcs.get( i + ":key" );
+
+            assertEquals( region + " data " + i, value );
+        }
+
+        // Remove all the items
+
+        for ( int i = 0; i <= items; i++ )
+        {
+            jcs.remove( i + ":key" );
+        }
+
+        // Verify removal
+
+        for ( int i = 0; i <= items; i++ )
+        {
+            assertNull( "Removed key should be null: " + i + ":key", jcs.get( i + ":key" ) );
+        }
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/ZeroSizeCacheUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/ZeroSizeCacheUnitTest.java
new file mode 100644
index 0000000..a277d57
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/ZeroSizeCacheUnitTest.java
@@ -0,0 +1,92 @@
+package org.apache.commons.jcs3;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/**
+ *
+ * @author Aaron Smuts
+ *
+ */
+public class ZeroSizeCacheUnitTest
+    extends TestCase
+{
+    /** number to get each loop */
+    private static int items = 20000;
+
+    /**
+     * Test setup
+     * <p>
+     * @throws Exception
+     */
+    @Override
+    public void setUp()
+        throws Exception
+    {
+        JCS.setConfigFilename( "/TestZeroSizeCache.ccf" );
+        JCS.getInstance( "testCache1" );
+    }
+
+    /**
+     * Verify that a 0 size cache does not result in errors. You should be able
+     * to disable a region this way.
+     * @throws Exception
+     *
+     */
+    public void testPutGetRemove()
+        throws Exception
+    {
+        CacheAccess<String, String> jcs = JCS.getInstance( "testCache1" );
+
+        for ( int i = 0; i <= items; i++ )
+        {
+            jcs.put( i + ":key", "data" + i );
+        }
+
+        // all the gets should be null
+        for ( int i = items; i >= 0; i-- )
+        {
+            String res = jcs.get( i + ":key" );
+            assertNull( "[" + i + ":key] should be null", res );
+        }
+
+        // test removal, should be no exceptions
+        jcs.remove( "300:key" );
+
+        // allow the shrinker to run
+        Thread.sleep( 500 );
+
+        // do it again.
+        for ( int i = 0; i <= items; i++ )
+        {
+            jcs.put( i + ":key", "data" + i );
+        }
+
+        for ( int i = items; i >= 0; i-- )
+        {
+            String res = jcs.get( i + ":key" );
+            assertNull( "[" + i + ":key] should be null", res );
+        }
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/access/CacheAccessUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/access/CacheAccessUnitTest.java
new file mode 100644
index 0000000..ba8dbf2
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/access/CacheAccessUnitTest.java
@@ -0,0 +1,367 @@
+package org.apache.commons.jcs3.access;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.access.exception.CacheException;
+import org.apache.commons.jcs3.access.exception.ObjectExistsException;
+import org.apache.commons.jcs3.engine.CompositeCacheAttributes;
+import org.apache.commons.jcs3.engine.ElementAttributes;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes;
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the methods of the cache access class.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class CacheAccessUnitTest
+    extends TestCase
+{
+    /**
+     * Verify that we get an object exists exception if the item is in the cache.
+     * @throws Exception
+     */
+    public void testPutSafe()
+        throws Exception
+    {
+        CacheAccess<String, String> access = JCS.getInstance( "test" );
+        assertNotNull( "We should have an access class", access );
+
+        String key = "mykey";
+        String value = "myvalue";
+
+        access.put( key, value );
+
+        String returnedValue1 = access.get( key );
+        assertEquals( "Wrong value returned.", value, returnedValue1 );
+
+        try
+        {
+            access.putSafe( key, "someothervalue" );
+            fail( "We should have received an exception since this key is already in the cache." );
+        }
+        catch ( CacheException e )
+        {
+            assertTrue( "Wrong type of exception.", e instanceof ObjectExistsException );
+            assertTrue( "Should have the key in the error message.", e.getMessage().indexOf( "[" + key + "]" ) != -1 );
+        }
+
+        String returnedValue2 = access.get( key );
+        assertEquals( "Wrong value returned.  Should still be the original.", value, returnedValue2 );
+    }
+
+    /**
+     * Try to put a null key and verify that we get an exception.
+     * @throws Exception
+     */
+    public void testPutNullKey()
+        throws Exception
+    {
+        CacheAccess<String, String> access = JCS.getInstance( "test" );
+        assertNotNull( "We should have an access class", access );
+
+        String key = null;
+        String value = "myvalue";
+
+        try
+        {
+            access.put( key, value );
+            fail( "Should not have been able to put a null key." );
+        }
+        catch ( CacheException e )
+        {
+            assertTrue( "Should have the word null in the error message.", e.getMessage().indexOf( "null" ) != -1 );
+        }
+    }
+
+    /**
+     * Try to put a null value and verify that we get an exception.
+     * @throws Exception
+     */
+    public void testPutNullValue()
+        throws Exception
+    {
+        CacheAccess<String, String> access = JCS.getInstance( "test" );
+        assertNotNull( "We should have an access class", access );
+
+        String key = "myKey";
+        String value = null;
+
+        try
+        {
+            access.put( key, value );
+            fail( "Should not have been able to put a null object." );
+        }
+        catch ( CacheException e )
+        {
+            assertTrue( "Should have the word null in the error message.", e.getMessage().indexOf( "null" ) != -1 );
+        }
+    }
+
+    /**
+     * Verify that elements that go in the region after this call take the new attributes.
+     * @throws Exception
+     */
+    public void testSetDefaultElementAttributes()
+        throws Exception
+    {
+        CacheAccess<String, String> access = JCS.getInstance( "test" );
+        assertNotNull( "We should have an access class", access );
+
+        long maxLife = 9876;
+        IElementAttributes attr = new ElementAttributes();
+        attr.setMaxLife(maxLife);
+
+        access.setDefaultElementAttributes( attr );
+
+        assertEquals( "Wrong element attributes.", attr.getMaxLife(), access.getDefaultElementAttributes()
+            .getMaxLife() );
+
+        String key = "mykey";
+        String value = "myvalue";
+
+        access.put( key, value );
+
+        ICacheElement<String, String> element = access.getCacheElement( key );
+
+        assertEquals( "Wrong max life.  Should have the new value.", maxLife, element.getElementAttributes()
+            .getMaxLife() );
+    }
+
+    /**
+     * Verify that getCacheElements returns the elements requested based on the key.
+     * @throws Exception
+     */
+    public void testGetCacheElements()
+        throws Exception
+    {
+        //SETUP
+        CacheAccess<String, String> access = JCS.getInstance( "test" );
+        assertNotNull( "We should have an access class", access );
+
+        String keyOne = "mykeyone";
+        String keyTwo = "mykeytwo";
+        String keyThree = "mykeythree";
+        String keyFour = "mykeyfour";
+        String valueOne = "myvalueone";
+        String valueTwo = "myvaluetwo";
+        String valueThree = "myvaluethree";
+        String valueFour = "myvaluefour";
+
+        access.put( keyOne, valueOne );
+        access.put( keyTwo, valueTwo );
+        access.put( keyThree, valueThree );
+
+        Set<String> input = new HashSet<>();
+        input.add( keyOne );
+        input.add( keyTwo );
+
+        //DO WORK
+        Map<String, ICacheElement<String, String>> result = access.getCacheElements( input );
+
+        //VERIFY
+        assertEquals( "map size", 2, result.size() );
+        ICacheElement<String, String> elementOne = result.get( keyOne );
+        assertEquals( "value one", keyOne, elementOne.getKey() );
+        assertEquals( "value one", valueOne, elementOne.getVal() );
+        ICacheElement<String, String> elementTwo = result.get( keyTwo );
+        assertEquals( "value two", keyTwo, elementTwo.getKey() );
+        assertEquals( "value two", valueTwo, elementTwo.getVal() );
+
+        assertNull(access.get(keyFour));
+        String suppliedValue1 = access.get(keyFour, () -> valueFour);
+        assertNotNull( "value four", suppliedValue1);
+        assertEquals( "value four", valueFour, suppliedValue1);
+        String suppliedValue2 = access.get(keyFour);
+        assertNotNull( "value four", suppliedValue2);
+        assertEquals( "value four", suppliedValue1, suppliedValue2);
+    }
+
+    /**
+     * Verify that we can get a region using the define region method.
+     * @throws Exception
+     */
+    public void testRegionDefiniton()
+        throws Exception
+    {
+        CacheAccess<String, String> access = JCS.getInstance( "test" );
+        assertNotNull( "We should have an access class", access );
+    }
+
+    /**
+     * Verify that we can get a region using the define region method with cache attributes.
+     * @throws Exception
+     */
+    public void testRegionDefinitonWithAttributes()
+        throws Exception
+    {
+        ICompositeCacheAttributes ca = new CompositeCacheAttributes();
+
+        long maxIdleTime = 8765;
+        ca.setMaxMemoryIdleTimeSeconds( maxIdleTime );
+
+        CacheAccess<String, String> access = JCS.getInstance( "testRegionDefinitonWithAttributes", ca );
+        assertNotNull( "We should have an access class", access );
+
+        ICompositeCacheAttributes ca2 = access.getCacheAttributes();
+        assertEquals( "Wrong idle time setting.", ca.getMaxMemoryIdleTimeSeconds(), ca2.getMaxMemoryIdleTimeSeconds() );
+    }
+
+    /**
+     * Verify that we can get a region using the define region method with cache attributes and
+     * element attributes.
+     * @throws Exception
+     */
+    public void testRegionDefinitonWithBothAttributes()
+        throws Exception
+    {
+        ICompositeCacheAttributes ca = new CompositeCacheAttributes();
+
+        long maxIdleTime = 8765;
+        ca.setMaxMemoryIdleTimeSeconds( maxIdleTime );
+
+        long maxLife = 9876;
+        IElementAttributes attr = new ElementAttributes();
+        attr.setMaxLife(maxLife);
+
+        CacheAccess<String, String> access = JCS.getInstance( "testRegionDefinitonWithAttributes", ca, attr );
+        assertNotNull( "We should have an access class", access );
+
+        ICompositeCacheAttributes ca2 = access.getCacheAttributes();
+        assertEquals( "Wrong idle time setting.", ca.getMaxMemoryIdleTimeSeconds(), ca2.getMaxMemoryIdleTimeSeconds() );
+    }
+
+    /**
+     * Verify we can get some matching elements..
+     * <p>
+     * @throws Exception
+     */
+    public void testGetMatching_Normal()
+        throws Exception
+    {
+        // SETUP
+        int maxMemorySize = 1000;
+        String keyprefix1 = "MyPrefix1";
+        String keyprefix2 = "MyPrefix2";
+        String memoryCacheClassName = "org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache";
+        ICompositeCacheAttributes cattr = new CompositeCacheAttributes();
+        cattr.setMemoryCacheName( memoryCacheClassName );
+        cattr.setMaxObjects( maxMemorySize );
+
+        long maxLife = 9876;
+        IElementAttributes attr = new ElementAttributes();
+        attr.setMaxLife(maxLife);
+
+        CacheAccess<String, Integer> access = JCS.getInstance( "testGetMatching_Normal", cattr, attr );
+
+        // DO WORK
+        int numToInsertPrefix1 = 10;
+        // insert with prefix1
+        for ( int i = 0; i < numToInsertPrefix1; i++ )
+        {
+            access.put( keyprefix1 + String.valueOf( i ), Integer.valueOf( i ) );
+        }
+
+        int numToInsertPrefix2 = 50;
+        // insert with prefix1
+        for ( int i = 0; i < numToInsertPrefix2; i++ )
+        {
+            access.put( keyprefix2 + String.valueOf( i ), Integer.valueOf( i ) );
+        }
+
+        Map<String, Integer> result1 = access.getMatching( keyprefix1 + ".+" );
+        Map<String, Integer> result2 = access.getMatching( keyprefix2 + "\\S+" );
+
+        // VERIFY
+        assertEquals( "Wrong number returned 1:", numToInsertPrefix1, result1.size() );
+        assertEquals( "Wrong number returned 2:", numToInsertPrefix2, result2.size() );
+        //System.out.println( result1 );
+
+        // verify that the elements are unwrapped
+        for (Map.Entry<String, Integer> entry : result1.entrySet())
+        {
+            Object value = entry.getValue();
+            assertFalse( "Should not be a cache element.", value instanceof ICacheElement );
+        }
+    }
+
+    /**
+     * Verify we can get some matching elements..
+     * <p>
+     * @throws Exception
+     */
+    public void testGetMatchingElements_Normal()
+        throws Exception
+    {
+        // SETUP
+        int maxMemorySize = 1000;
+        String keyprefix1 = "MyPrefix1";
+        String keyprefix2 = "MyPrefix2";
+        String memoryCacheClassName = "org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache";
+        ICompositeCacheAttributes cattr = new CompositeCacheAttributes();
+        cattr.setMemoryCacheName( memoryCacheClassName );
+        cattr.setMaxObjects( maxMemorySize );
+
+        long maxLife = 9876;
+        IElementAttributes attr = new ElementAttributes();
+        attr.setMaxLife(maxLife);
+
+        CacheAccess<String, Integer> access = JCS.getInstance( "testGetMatching_Normal", cattr, attr );
+
+        // DO WORK
+        int numToInsertPrefix1 = 10;
+        // insert with prefix1
+        for ( int i = 0; i < numToInsertPrefix1; i++ )
+        {
+            access.put( keyprefix1 + String.valueOf( i ), Integer.valueOf( i ) );
+        }
+
+        int numToInsertPrefix2 = 50;
+        // insert with prefix1
+        for ( int i = 0; i < numToInsertPrefix2; i++ )
+        {
+            access.put( keyprefix2 + String.valueOf( i ), Integer.valueOf( i ) );
+        }
+
+        Map<String, ICacheElement<String, Integer>> result1 = access.getMatchingCacheElements( keyprefix1 + "\\S+" );
+        Map<String, ICacheElement<String, Integer>> result2 = access.getMatchingCacheElements( keyprefix2 + ".+" );
+
+        // VERIFY
+        assertEquals( "Wrong number returned 1:", numToInsertPrefix1, result1.size() );
+        assertEquals( "Wrong number returned 2:", numToInsertPrefix2, result2.size() );
+        //System.out.println( result1 );
+
+        // verify that the elements are wrapped
+        for (Map.Entry<String, ICacheElement<String, Integer>> entry : result1.entrySet())
+        {
+            Object value = entry.getValue();
+            assertTrue( "Should be a cache element.", value instanceof ICacheElement );
+        }
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/access/GroupCacheAccessUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/access/GroupCacheAccessUnitTest.java
new file mode 100644
index 0000000..c690b76
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/access/GroupCacheAccessUnitTest.java
@@ -0,0 +1,241 @@
+package org.apache.commons.jcs3.access;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Set;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.GroupCacheAccess;
+import org.apache.commons.jcs3.access.exception.CacheException;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the methods of the group cache access class.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class GroupCacheAccessUnitTest
+    extends TestCase
+{
+    /**
+     * Verify that we can put and get an object
+     * @throws Exception
+     */
+    public void testPutAndGet()
+        throws Exception
+    {
+        GroupCacheAccess<String, String> access = JCS.getGroupCacheInstance( "test" );
+        assertNotNull( "We should have an access class", access );
+
+        String key = "mykey";
+        String group = "mygroup";
+        String value = "myvalue";
+
+        access.putInGroup(key, group, value);
+
+        String returnedValue1 = access.getFromGroup(key, group);
+        assertEquals( "Wrong value returned.", value, returnedValue1 );
+    }
+
+    /**
+     * Try to put a null key and verify that we get an exception.
+     * @throws Exception
+     */
+    public void testPutNullKey()
+        throws Exception
+    {
+        GroupCacheAccess<String, String> access = JCS.getGroupCacheInstance( "test" );
+        assertNotNull( "We should have an access class", access );
+
+        String key = null;
+        String group = "mygroup";
+        String value = "myvalue";
+
+        try
+        {
+            access.putInGroup(key, group, value);
+            fail( "Should not have been able to put a null key." );
+        }
+        catch ( CacheException e )
+        {
+            assertTrue( "Should have the word null in the error message.", e.getMessage().indexOf( "null" ) != -1 );
+        }
+    }
+
+    /**
+     * Try to put a null value and verify that we get an exception.
+     * @throws Exception
+     */
+    public void testPutNullValue()
+        throws Exception
+    {
+        GroupCacheAccess<String, String> access = JCS.getGroupCacheInstance( "test" );
+        assertNotNull( "We should have an access class", access );
+
+        String key = "myKey";
+        String group = "mygroup";
+        String value = null;
+
+        try
+        {
+            access.putInGroup(key, group, value);
+            fail( "Should not have been able to put a null object." );
+        }
+        catch ( CacheException e )
+        {
+            assertTrue( "Should have the word null in the error message.", e.getMessage().indexOf( "null" ) != -1 );
+        }
+    }
+
+    /**
+     * Verify that we can remove items from the cache
+     * @throws Exception
+     */
+    public void testRemove()
+        throws Exception
+    {
+        GroupCacheAccess<String, String> access = JCS.getGroupCacheInstance( "test" );
+        assertNotNull( "We should have an access class", access );
+
+        String key = "mykey";
+        String group = "mygroup";
+        String value = "myvalue";
+
+        for (int i = 0; i < 10; i++)
+        {
+            access.putInGroup(key + i, group, value + i);
+        }
+
+        // Make sure cache contains some data
+        for (int i = 0; i < 10; i++)
+        {
+            String returnedValue1 = access.getFromGroup(key + i, group);
+            assertEquals( "Wrong value returned.", value + i, returnedValue1 );
+        }
+
+        access.removeFromGroup(key + 0, group);
+
+        assertNull("Should not be in cache", access.getFromGroup(key + 0, group));
+
+        for (int i = 1; i < 10; i++)
+        {
+            String returnedValue1 = access.getFromGroup(key + i, group);
+            assertEquals( "Wrong value returned.", value + i, returnedValue1 );
+        }
+    }
+
+    /**
+     * Verify that we can invalidate the group
+     * @throws Exception
+     */
+    public void testInvalidate()
+        throws Exception
+    {
+        GroupCacheAccess<String, String> access = JCS.getGroupCacheInstance( "test" );
+        assertNotNull( "We should have an access class", access );
+
+        String key = "mykey";
+        String group = "mygroup";
+        String value = "myvalue";
+
+        for (int i = 0; i < 10; i++)
+        {
+            access.putInGroup(key + i, group + 0, value + i);
+        }
+
+        for (int i = 0; i < 10; i++)
+        {
+            access.putInGroup(key + i, group + 1, value + i);
+        }
+
+        // Make sure cache contains some data
+        for (int i = 0; i < 10; i++)
+        {
+            String returnedValue1 = access.getFromGroup(key + i, group + 0);
+            assertEquals( "Wrong value returned.", value + i, returnedValue1 );
+            String returnedValue2 = access.getFromGroup(key + i, group + 1);
+            assertEquals( "Wrong value returned.", value + i, returnedValue2 );
+        }
+
+        access.invalidateGroup(group + 0);
+
+        for (int i = 0; i < 10; i++)
+        {
+            assertNull("Should not be in cache", access.getFromGroup(key + i, group + 0));
+        }
+
+        for (int i = 0; i < 10; i++)
+        {
+            String returnedValue1 = access.getFromGroup(key + i, group + 1);
+            assertEquals( "Wrong value returned.", value + i, returnedValue1 );
+        }
+    }
+
+    /**
+     * Verify we can use the group cache.
+     * <p>
+     * @throws Exception
+     */
+    public void testGroupCache()
+        throws Exception
+    {
+        GroupCacheAccess<String, Integer> access = JCS.getGroupCacheInstance( "testGroup" );
+        String groupName1 = "testgroup1";
+        String groupName2 = "testgroup2";
+
+        Set<String> keys1 = access.getGroupKeys( groupName1 );
+        assertNotNull(keys1);
+        assertEquals(0, keys1.size());
+
+        Set<String> keys2 = access.getGroupKeys( groupName2 );
+        assertNotNull(keys2);
+        assertEquals(0, keys2.size());
+
+        // DO WORK
+        int numToInsertGroup1 = 10;
+        // insert with prefix1
+        for ( int i = 0; i < numToInsertGroup1; i++ )
+        {
+            access.putInGroup(String.valueOf( i ), groupName1, Integer.valueOf( i ) );
+        }
+
+        int numToInsertGroup2 = 50;
+        // insert with prefix1
+        for ( int i = 0; i < numToInsertGroup2; i++ )
+        {
+            access.putInGroup(String.valueOf( i ), groupName2, Integer.valueOf( i + 1 ) );
+        }
+
+        keys1 = access.getGroupKeys( groupName1 ); // Test for JCS-102
+        assertNotNull(keys1);
+        assertEquals("Wrong number returned 1:", 10, keys1.size());
+
+        keys2 = access.getGroupKeys( groupName2 );
+        assertNotNull(keys2);
+        assertEquals("Wrong number returned 2:", 50, keys2.size());
+
+        assertEquals(Integer.valueOf(5), access.getFromGroup("5", groupName1));
+        assertEquals(Integer.valueOf(6), access.getFromGroup("5", groupName2));
+
+        assertTrue(access.getGroupNames().contains(groupName1));
+        assertTrue(access.getGroupNames().contains(groupName2));
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/access/SystemPropertyUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/access/SystemPropertyUnitTest.java
new file mode 100644
index 0000000..f5878b6
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/access/SystemPropertyUnitTest.java
@@ -0,0 +1,90 @@
+package org.apache.commons.jcs3.access;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.engine.control.CompositeCacheManager;
+import org.junit.FixMethodOrder;
+import org.junit.runners.MethodSorters;
+
+/**
+ * This test is for the system property usage in configuration values.
+ *
+ * @author Aaron Smuts
+ *
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class SystemPropertyUnitTest
+    extends TestCase
+{
+
+    /**
+     * Verify that we use a system property for a ${FOO} string in a value.
+     *
+     * @throws Exception
+     *
+     */
+    public void test1SystemPropertyInValueDelimiter()
+        throws Exception
+    {
+
+        int maxMemory = 1234;
+        System.getProperties().setProperty( "MY_SYSTEM_PROPERTY_DISK_DIR", "system_set" );
+        System.getProperties().setProperty( "MY_SYSTEM_PROPERTY_MAX_SIZE", String.valueOf( maxMemory ) );
+
+        JCS.setConfigFilename( "/TestSystemProperties.ccf" );
+
+        CacheAccess<String, String> cache = JCS.getInstance( "test1" );
+        assertEquals( "We should have used the system property for the memory size", maxMemory, cache
+            .getCacheAttributes().getMaxObjects() );
+
+        System.clearProperty("MY_SYSTEM_PROPERTY_DISK_DIR");
+        System.clearProperty("MY_SYSTEM_PROPERTY_MAX_SIZE");
+    }
+
+    /**
+     * Verify that we use a system property for a ${FOO} string in a value. We
+     * define a propety in the cache.ccf file, but we do not have it as a system
+     * property. The default value should be used, if one exists.
+     *
+     * @throws Exception
+     *
+     */
+    public void test2SystemPropertyMissingInValueDelimeter()
+        throws Exception
+    {
+        System.getProperties().setProperty( "MY_SYSTEM_PROPERTY_DISK_DIR", "system_set" );
+
+        CompositeCacheManager mgr = CompositeCacheManager.getUnconfiguredInstance();
+        mgr.configure( "/TestSystemProperties.ccf" );
+
+        CacheAccess<String, String> cache = JCS.getInstance( "missing" );
+        // TODO check against the actual default def
+        assertEquals( "We should have used the default property for the memory size", 100, cache.getCacheAttributes()
+            .getMaxObjects() );
+
+        System.clearProperty("MY_SYSTEM_PROPERTY_DISK_DIR");
+
+    }
+
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/access/TestCacheAccess.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/access/TestCacheAccess.java
new file mode 100644
index 0000000..c08f64a
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/access/TestCacheAccess.java
@@ -0,0 +1,959 @@
+package org.apache.commons.jcs3.access;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Random;
+import java.util.StringTokenizer;
+
+import org.apache.commons.jcs3.engine.control.event.ElementEventHandlerMockImpl;
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.access.GroupCacheAccess;
+import org.apache.commons.jcs3.access.exception.CacheException;
+import org.apache.commons.jcs3.engine.ElementAttributes;
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+import org.apache.commons.jcs3.engine.control.CompositeCacheManager;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * Allows the user to run common cache commands from the command line for a test cache. This also
+ * provide basic methods for use in unit tests.
+ */
+public class TestCacheAccess
+{
+    /** log instance */
+    private static final Log log = LogManager.getLog( TestCacheAccess.class );
+
+    /** cache instance to use in testing */
+    private CacheAccess<String, String> cache_control = null;
+
+    /** cache instance to use in testing */
+    private GroupCacheAccess<String, String> group_cache_control = null;
+
+    /** do we use system.out.println to print out debug data? */
+    private static boolean isSysOut = false;
+
+    /** Construct and initialize the cachecontrol based on the config file. */
+    public TestCacheAccess()
+    {
+        this( "testCache1" );
+    }
+
+    /**
+     * @param regionName the name of the region.
+     */
+    public TestCacheAccess( String regionName )
+    {
+        try
+        {
+            cache_control = JCS.getInstance( regionName );
+            group_cache_control = JCS.getGroupCacheInstance( regionName );
+        }
+        catch ( Exception e )
+        {
+            log.error( "Problem getting cache instance", e );
+            p( e.toString() );
+        }
+    }
+
+    /**
+     * This is the main loop called by the main method.
+     */
+    public void runLoop()
+    {
+        try
+        {
+            // process user input till done
+            boolean notDone = true;
+            String message = null;
+            // wait to dispose
+            BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) );
+
+            help();
+
+            while ( notDone )
+            {
+                p( "enter command:" );
+
+                message = br.readLine();
+
+                if ( message == null || message.startsWith( "help" ) )
+                {
+                    help();
+                }
+                else if ( message.startsWith( "gc" ) )
+                {
+                    System.gc();
+                }
+                else if ( message.startsWith( "getAttributeNames" ) )
+                {
+                    long n_start = System.currentTimeMillis();
+                    String groupName = null;
+                    StringTokenizer toke = new StringTokenizer( message );
+                    int tcnt = 0;
+                    while ( toke.hasMoreElements() )
+                    {
+                        tcnt++;
+                        String t = (String) toke.nextElement();
+                        if ( tcnt == 2 )
+                        {
+                            groupName = t.trim();
+                        }
+                    }
+                    getAttributeNames( groupName );
+                    long n_end = System.currentTimeMillis();
+                    p( "---got attrNames for " + groupName + " in " + String.valueOf( n_end - n_start ) + " millis ---" );
+                }
+                else if ( message.startsWith( "shutDown" ) )
+                {
+                    CompositeCacheManager.getInstance().shutDown();
+                    //cache_control.dispose();
+                    notDone = false;
+                    //System.exit( -1 );
+                    return;
+                }
+                /////////////////////////////////////////////////////////////////////
+                // get multiple from a region
+                else if ( message.startsWith( "getm" ) )
+                {
+                    processGetMultiple( message );
+                }
+                else if ( message.startsWith( "getg" ) )
+                {
+                    processGetGroup( message );
+                }
+                else if ( message.startsWith( "getag" ) )
+                {
+                    processGetAutoGroup( message );
+                }
+                else if ( message.startsWith( "getMatching" ) )
+                {
+                    processGetMatching( message );
+                }
+                else if ( message.startsWith( "get" ) )
+                {
+                    processGet( message );
+                }
+                else if ( message.startsWith( "putg" ) )
+                {
+                    processPutGroup( message );
+                }
+                // put automatically
+                else if ( message.startsWith( "putag" ) )
+                {
+                    processPutAutoGroup( message );
+                }
+                else if ( message.startsWith( "putm" ) )
+                {
+                    String numS = message.substring( message.indexOf( " " ) + 1, message.length() );
+                    if ( numS == null )
+                    {
+                        p( "usage: putm numbertoput" );
+                    }
+                    else
+                    {
+                        int num = Integer.parseInt( numS.trim() );
+                        putMultiple( num );
+                    }
+                }
+                else if ( message.startsWith( "pute" ) )
+                {
+                    String numS = message.substring( message.indexOf( " " ) + 1, message.length() );
+                    if ( numS == null )
+                    {
+                        p( "usage: putme numbertoput" );
+                    }
+                    else
+                    {
+                        int num = Integer.parseInt( numS.trim() );
+                        long n_start = System.currentTimeMillis();
+                        for ( int n = 0; n < num; n++ )
+                        {
+                            IElementAttributes attrp = cache_control.getDefaultElementAttributes();
+                            ElementEventHandlerMockImpl hand = new ElementEventHandlerMockImpl();
+                            attrp.addElementEventHandler( hand );
+                            cache_control.put( "key" + n, "data" + n + " put from ta = junk", attrp );
+                        }
+                        long n_end = System.currentTimeMillis();
+                        p( "---put " + num + " in " + String.valueOf( n_end - n_start ) + " millis ---" );
+                    }
+                }
+                else if ( message.startsWith( "put" ) )
+                {
+                    processPut( message );
+                }
+                else if ( message.startsWith( "removem" ) )
+                {
+                    String numS = message.substring( message.indexOf( " " ) + 1, message.length() );
+                    if ( numS == null )
+                    {
+                        p( "usage: removem numbertoremove" );
+                    }
+                    else
+                    {
+                        int num = Integer.parseInt( numS.trim() );
+                        removeMultiple( num );
+                    }
+                }
+                else if ( message.startsWith( "removeall" ) )
+                {
+                    cache_control.clear();
+                    p( "removed all" );
+                }
+                else if ( message.startsWith( "remove" ) )
+                {
+                    String key = message.substring( message.indexOf( " " ) + 1, message.length() );
+                    cache_control.remove( key );
+                    p( "removed " + key );
+                }
+                else if ( message.startsWith( "deattr" ) )
+                {
+                    IElementAttributes ae = cache_control.getDefaultElementAttributes();
+                    p( "Default IElementAttributes " + ae );
+                }
+                else if ( message.startsWith( "cloneattr" ) )
+                {
+                    String numS = message.substring( message.indexOf( " " ) + 1, message.length() );
+                    if ( numS == null )
+                    {
+                        p( "usage: put numbertoput" );
+                    }
+                    else
+                    {
+                        int num = Integer.parseInt( numS.trim() );
+                        IElementAttributes attrp = new ElementAttributes();
+                        long n_start = System.currentTimeMillis();
+                        for ( int n = 0; n < num; n++ )
+                        {
+                            attrp.clone();
+                        }
+                        long n_end = System.currentTimeMillis();
+                        p( "---cloned attr " + num + " in " + String.valueOf( n_end - n_start ) + " millis ---" );
+                    }
+                }
+                else if ( message.startsWith( "switch" ) )
+                {
+                    String name = message.substring( message.indexOf( " " ) + 1, message.length() );
+
+                    setRegion( name );
+                    p( "switched to cache = " + name );
+                    p( cache_control.toString() );
+                }
+                else if ( message.startsWith( "stats" ) )
+                {
+                    p( cache_control.getStats() );
+                }
+                else if ( message.startsWith( "gc" ) )
+                {
+                    System.gc();
+                    p( "Called system.gc()" );
+                }
+                else if ( message.startsWith( "random" ) )
+                {
+                    processRandom( message );
+                }
+            }
+        }
+        catch ( CacheException | IOException e )
+        {
+            p( e.toString() );
+            e.printStackTrace( System.out );
+        }
+    }
+
+    /**
+     * @param message
+     */
+    private void processGetMultiple( String message )
+    {
+        int num = 0;
+        boolean show = true;
+
+        StringTokenizer toke = new StringTokenizer( message );
+        int tcnt = 0;
+        while ( toke.hasMoreElements() )
+        {
+            tcnt++;
+            String t = (String) toke.nextElement();
+            if ( tcnt == 2 )
+            {
+                try
+                {
+                    num = Integer.parseInt( t.trim() );
+                }
+                catch ( NumberFormatException nfe )
+                {
+                    p( t + "not a number" );
+                }
+            }
+            else if ( tcnt == 3 )
+            {
+                show = Boolean.valueOf( t ).booleanValue();
+            }
+        }
+
+        if ( tcnt < 2 )
+        {
+            p( "usage: get numbertoget show values[true|false]" );
+        }
+        else
+        {
+            getMultiple( num, show );
+        }
+    }
+
+    /**
+     * @param message
+     */
+    private void processGetGroup( String message )
+    {
+        String key = null;
+        String group = null;
+        boolean show = true;
+
+        StringTokenizer toke = new StringTokenizer( message );
+        int tcnt = 0;
+        while ( toke.hasMoreElements() )
+        {
+            tcnt++;
+            String t = (String) toke.nextElement();
+            if ( tcnt == 2 )
+            {
+                key = t.trim();
+            }
+            else if ( tcnt == 3 )
+            {
+                group = t.trim();
+            }
+            else if ( tcnt == 4 )
+            {
+                show = Boolean.valueOf( t ).booleanValue();
+            }
+        }
+
+        if ( tcnt < 2 )
+        {
+            p( "usage: get key show values[true|false]" );
+        }
+        else
+        {
+            long n_start = System.currentTimeMillis();
+            try
+            {
+                Object obj = group_cache_control.getFromGroup( key, group );
+                if ( show && obj != null )
+                {
+                    p( obj.toString() );
+                }
+            }
+            catch ( Exception e )
+            {
+                log.error( e );
+            }
+            long n_end = System.currentTimeMillis();
+            p( "---got " + key + " from group " + group + " in " + String.valueOf( n_end - n_start ) + " millis ---" );
+        }
+    }
+
+    /**
+     * @param message
+     */
+    private void processGetAutoGroup( String message )
+    {
+        // get auto from group
+
+        int num = 0;
+        String group = null;
+        boolean show = true;
+
+        StringTokenizer toke = new StringTokenizer( message );
+        int tcnt = 0;
+        while ( toke.hasMoreElements() )
+        {
+            tcnt++;
+            String t = (String) toke.nextElement();
+            if ( tcnt == 2 )
+            {
+                num = Integer.parseInt( t.trim() );
+            }
+            else if ( tcnt == 3 )
+            {
+                group = t.trim();
+            }
+            else if ( tcnt == 4 )
+            {
+                show = Boolean.valueOf( t ).booleanValue();
+            }
+        }
+
+        if ( tcnt < 2 )
+        {
+            p( "usage: get key show values[true|false]" );
+        }
+        else
+        {
+            long n_start = System.currentTimeMillis();
+            try
+            {
+                for ( int a = 0; a < num; a++ )
+                {
+                    Object obj = group_cache_control.getFromGroup( "keygr" + a, group );
+                    if ( show && obj != null )
+                    {
+                        p( obj.toString() );
+                    }
+                }
+            }
+            catch ( Exception e )
+            {
+                log.error( e );
+            }
+            long n_end = System.currentTimeMillis();
+            p( "---got " + num + " from group " + group + " in " + String.valueOf( n_end - n_start ) + " millis ---" );
+        }
+    }
+
+    /**
+     * @param message
+     * @throws CacheException
+     */
+    private void processPutGroup( String message )
+        throws CacheException
+    {
+        String group = null;
+        String key = null;
+        StringTokenizer toke = new StringTokenizer( message );
+        int tcnt = 0;
+        while ( toke.hasMoreElements() )
+        {
+            tcnt++;
+            String t = (String) toke.nextElement();
+            if ( tcnt == 2 )
+            {
+                key = t.trim();
+            }
+            else if ( tcnt == 3 )
+            {
+                group = t.trim();
+            }
+        }
+
+        if ( tcnt < 3 )
+        {
+            p( "usage: putg key group" );
+        }
+        else
+        {
+            long n_start = System.currentTimeMillis();
+            group_cache_control.putInGroup( key, group, "data from putg ----asdfasfas-asfasfas-asfas in group " + group );
+            long n_end = System.currentTimeMillis();
+            p( "---put " + key + " in group " + group + " in " + String.valueOf( n_end - n_start ) + " millis ---" );
+        }
+    }
+
+    /**
+     * @param message
+     * @throws CacheException
+     */
+    private void processPutAutoGroup( String message )
+        throws CacheException
+    {
+        String group = null;
+        int num = 0;
+        StringTokenizer toke = new StringTokenizer( message );
+        int tcnt = 0;
+        while ( toke.hasMoreElements() )
+        {
+            tcnt++;
+            String t = (String) toke.nextElement();
+            if ( tcnt == 2 )
+            {
+                num = Integer.parseInt( t.trim() );
+            }
+            else if ( tcnt == 3 )
+            {
+                group = t.trim();
+            }
+        }
+
+        if ( tcnt < 3 )
+        {
+            p( "usage: putag num group" );
+        }
+        else
+        {
+            long n_start = System.currentTimeMillis();
+            for ( int a = 0; a < num; a++ )
+            {
+                group_cache_control.putInGroup( "keygr" + a, group, "data " + a
+                    + " from putag ----asdfasfas-asfasfas-asfas in group " + group );
+            }
+            long n_end = System.currentTimeMillis();
+            p( "---put " + num + " in group " + group + " in " + String.valueOf( n_end - n_start ) + " millis ---" );
+        }
+    }
+
+    /**
+     * @param message
+     * @throws CacheException
+     */
+    private void processPut( String message )
+        throws CacheException
+    {
+        String key = null;
+        String val = null;
+        StringTokenizer toke = new StringTokenizer( message );
+        int tcnt = 0;
+        while ( toke.hasMoreElements() )
+        {
+            tcnt++;
+            String t = (String) toke.nextElement();
+            if ( tcnt == 2 )
+            {
+                key = t.trim();
+            }
+            else if ( tcnt == 3 )
+            {
+                val = t.trim();
+            }
+        }
+
+        if ( tcnt < 3 )
+        {
+            p( "usage: put key val" );
+        }
+        else
+        {
+
+            long n_start = System.currentTimeMillis();
+            cache_control.put( key, val );
+            long n_end = System.currentTimeMillis();
+            p( "---put " + key + " | " + val + " in " + String.valueOf( n_end - n_start ) + " millis ---" );
+        }
+    }
+
+    /**
+     * @param message
+     */
+    private void processRandom( String message )
+    {
+        String rangeS = "";
+        String numOpsS = "";
+        boolean show = true;
+
+        StringTokenizer toke = new StringTokenizer( message );
+        int tcnt = 0;
+        while ( toke.hasMoreElements() )
+        {
+            tcnt++;
+            String t = (String) toke.nextElement();
+            if ( tcnt == 2 )
+            {
+                rangeS = t.trim();
+            }
+            else if ( tcnt == 3 )
+            {
+                numOpsS = t.trim();
+            }
+            else if ( tcnt == 4 )
+            {
+                show = Boolean.valueOf( t ).booleanValue();
+            }
+        }
+
+        String numS = message.substring( message.indexOf( " " ) + 1, message.length() );
+
+        int range = 0;
+        int numOps = 0;
+        try
+        {
+            range = Integer.parseInt( rangeS.trim() );
+            numOps = Integer.parseInt( numOpsS.trim() );
+        }
+        catch ( Exception e )
+        {
+            p( "usage: random range numOps show" );
+            p( "ex.  random 100 1000 false" );
+        }
+        if ( numS == null )
+        {
+            p( "usage: random range numOps show" );
+            p( "ex.  random 100 1000 false" );
+        }
+        else
+        {
+            random( range, numOps, show );
+        }
+    }
+
+    /**
+     * @param message
+     */
+    private void processGet( String message )
+    {
+        // plain old get
+
+        String key = null;
+        boolean show = true;
+
+        StringTokenizer toke = new StringTokenizer( message );
+        int tcnt = 0;
+        while ( toke.hasMoreElements() )
+        {
+            tcnt++;
+            String t = (String) toke.nextElement();
+            if ( tcnt == 2 )
+            {
+                key = t.trim();
+            }
+            else if ( tcnt == 3 )
+            {
+                show = Boolean.valueOf( t ).booleanValue();
+            }
+        }
+
+        if ( tcnt < 2 )
+        {
+            p( "usage: get key show values[true|false]" );
+        }
+        else
+        {
+            long n_start = System.currentTimeMillis();
+            try
+            {
+                Object obj = cache_control.get( key );
+                if ( show && obj != null )
+                {
+                    p( obj.toString() );
+                }
+            }
+            catch ( Exception e )
+            {
+                log.error( e );
+            }
+            long n_end = System.currentTimeMillis();
+            p( "---got " + key + " in " + String.valueOf( n_end - n_start ) + " millis ---" );
+        }
+    }
+
+    /**
+     * @param message
+     */
+    private void processGetMatching( String message )
+    {
+        // plain old get
+
+        String pattern = null;
+        boolean show = true;
+
+        StringTokenizer toke = new StringTokenizer( message );
+        int tcnt = 0;
+        while ( toke.hasMoreElements() )
+        {
+            tcnt++;
+            String t = (String) toke.nextElement();
+            if ( tcnt == 2 )
+            {
+                pattern = t.trim();
+            }
+            else if ( tcnt == 3 )
+            {
+                show = Boolean.valueOf( t ).booleanValue();
+            }
+        }
+
+        if ( tcnt < 2 )
+        {
+            p( "usage: getMatching key show values[true|false]" );
+        }
+        else
+        {
+            long n_start = System.currentTimeMillis();
+            try
+            {
+                Map<String, String> results = cache_control.getMatching( pattern );
+                if ( show && results != null )
+                {
+                    p( results.toString() );
+                }
+            }
+            catch ( Exception e )
+            {
+                log.error( e );
+            }
+            long n_end = System.currentTimeMillis();
+            p( "---gotMatching [" + pattern + "] in " + String.valueOf( n_end - n_start ) + " millis ---" );
+        }
+    }
+
+    /**
+     * Test harness.
+     * @param args The command line arguments
+     */
+    public static void main( String[] args )
+    {
+        isSysOut = true;
+        String ccfFileName = args[0];
+        if ( ccfFileName != null )
+        {
+            JCS.setConfigFilename( ccfFileName );
+        }
+        TestCacheAccess tca = new TestCacheAccess( "testCache1" );
+        tca.runLoop();
+    }
+
+    // end main
+    /////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Gets multiple items from the cache with keys of the form key1, key2, key3 up to key[num].
+     * @param num int
+     */
+    public void getMultiple( int num )
+    {
+        getMultiple( num, false );
+    }
+
+    /**
+     * @param num
+     * @param show
+     */
+    public void getMultiple( int num, boolean show )
+    {
+        long n_start = System.currentTimeMillis();
+        for ( int n = 0; n < num; n++ )
+        {
+            try
+            {
+                Object obj = cache_control.get( "key" + n );
+                if ( show && obj != null )
+                {
+                    p( obj.toString() );
+                }
+            }
+            catch ( Exception e )
+            {
+                log.error( e );
+            }
+        }
+        long n_end = System.currentTimeMillis();
+        p( "---got " + num + " in " + String.valueOf( n_end - n_start ) + " millis ---" );
+    }
+
+    /**
+     * Puts multiple items into the cache.
+     * @param num int
+     */
+    public void putMultiple( int num )
+    {
+        try
+        {
+            long n_start = System.currentTimeMillis();
+            for ( int n = 0; n < num; n++ )
+            {
+                cache_control.put( "key" + n, "data" + n + " put from ta = junk" );
+            }
+            long n_end = System.currentTimeMillis();
+            p( "---put " + num + " in " + String.valueOf( n_end - n_start ) + " millis ---" );
+        }
+        catch ( Exception e )
+        {
+            log.error( e );
+        }
+    }
+
+    /**
+     * Removes multiple items from the cache.
+     * @param num int
+     */
+    public void removeMultiple( int num )
+    {
+        try
+        {
+            long n_start = System.currentTimeMillis();
+            for ( int n = 0; n < num; n++ )
+            {
+                cache_control.remove( "key" + n );
+            }
+            long n_end = System.currentTimeMillis();
+            p( "---removed " + num + " in " + String.valueOf( n_end - n_start ) + " millis ---" );
+        }
+        catch ( Exception e )
+        {
+            log.error( e );
+        }
+    }
+
+    /**
+     * The random method performs numOps number of operations. The operations will be a mix of puts,
+     * gets, and removes. The key range will be from 0 to range.
+     * @param range int The end of the key range.
+     * @param numOps int The number of operations to perform
+     */
+    public void random( int range, int numOps )
+    {
+        random( range, numOps, false );
+    }
+
+    /**
+     * @param range
+     * @param numOps
+     * @param show
+     */
+    public void random( int range, int numOps, boolean show )
+    {
+        try
+        {
+            for ( int i = 1; i < numOps; i++ )
+            {
+                Random ran = new Random( i );
+                int n = ran.nextInt( 4 );
+                int kn = ran.nextInt( range );
+                String key = "key" + kn;
+                if ( n == 1 )
+                {
+                    cache_control.put( key, "data" + i + " junk asdfffffffadfasdfasf " + kn + ":" + n );
+                    if ( show )
+                    {
+                        p( "put " + key );
+                    }
+                }
+                else if ( n == 2 )
+                {
+                    cache_control.remove( key );
+                    if ( show )
+                    {
+                        p( "removed " + key );
+                    }
+                }
+                else
+                {
+                    // slightly greater chance of get
+                    Object obj = cache_control.get( key );
+                    if ( show && obj != null )
+                    {
+                        p( obj.toString() );
+                    }
+                }
+
+                if ( i % 10000 == 0 )
+                {
+                    p( cache_control.getStats() );
+                }
+
+            }
+            p( "Finished random cycle of " + numOps );
+        }
+        catch ( Exception e )
+        {
+            p( e.toString() );
+            e.printStackTrace( System.out );
+        }
+    }
+
+    /**
+     * Sets the region to be used by test methods.
+     * @param name String -- Name of region
+     */
+    public void setRegion( String name )
+    {
+        try
+        {
+            cache_control = JCS.getInstance( name );
+        }
+        catch ( Exception e )
+        {
+            p( e.toString() );
+            e.printStackTrace( System.out );
+        }
+
+    }
+
+    /////////////////////////////////////////////////////////////////////////////
+    /**
+     * The tester will print to the console if isSysOut is true, else it will log. It is false by
+     * default. When run via the main method, isSysOut will be set to true
+     * @param s String to print or log
+     */
+    public static void p( String s )
+    {
+        if ( isSysOut )
+        {
+            System.out.println( s );
+        }
+        else
+        {
+            if ( log.isDebugEnabled() )
+            {
+                log.debug( s );
+            }
+        }
+    }
+
+    /**
+     * Displays usage information for command line testing.
+     */
+    public static void help()
+    {
+        p( "\n\n\n\n" );
+        p( "type 'shutDown' to shutdown the cache" );
+        p( "type 'getm num show[false|true]' to get num automatically from a region" );
+        p( "type 'putm num' to put num automatically to a region" );
+        p( "type 'removeall' to remove all items in a region" );
+        p( "type 'remove key' to remove" );
+        p( "type 'removem num' to remove a number automatically" );
+        p( "type 'getMatching pattern show' to getMatching" );
+        p( "type 'get key show' to get" );
+        p( "type 'getg key group show' to get" );
+        p( "type 'getag num group show' to get automatically from a group" );
+        p( "type 'getAttributeNames group' to get a list og the group elements" );
+        p( "type 'putg key group val' to put" );
+        p( "type 'putag num group' to put automatically from a group" );
+        p( "type 'put key val' to put" );
+        p( "type 'stats' to get stats" );
+        p( "type 'deattr' to get the default element attributes" );
+        p( "type 'cloneattr num' to clone attr" );
+        p( "type 'random range numOps' to put, get, and remove randomly" );
+        p( "type 'switch name' to switch to this region name" );
+        p( "type 'gc' to call System.gc()" );
+        p( "type 'help' for commands" );
+
+    }
+
+    /**
+     * Gets the attributeNames attribute of the TestCacheAccess class
+     * @param groupName
+     */
+    public void getAttributeNames( String groupName )
+    {
+        Iterator<String> iter = group_cache_control.getGroupKeys( groupName ).iterator();
+
+        while ( iter.hasNext() )
+        {
+            p( "=" + iter.next() );
+        }
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/admin/AdminBeanUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/admin/AdminBeanUnitTest.java
new file mode 100644
index 0000000..ef7a0bb
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/admin/AdminBeanUnitTest.java
@@ -0,0 +1,176 @@
+package org.apache.commons.jcs3.admin;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.List;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.admin.CacheElementInfo;
+import org.apache.commons.jcs3.admin.CacheRegionInfo;
+import org.apache.commons.jcs3.admin.JCSAdminBean;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/**
+ * Test the admin bean that is used by the JCSAdmin.jsp
+ *
+ * @author Aaron Smuts
+ *
+ */
+public class AdminBeanUnitTest
+    extends TestCase
+{
+
+    /**
+     * Create a test region and then verify that we get it from the list.
+     *
+     * @throws Exception
+     *
+     */
+    public void testGetRegionInfo()
+        throws Exception
+    {
+        String regionName = "myRegion";
+        CacheAccess<String, String> cache = JCS.getInstance( regionName );
+
+        cache.put( "key", "value" );
+
+        JCSAdminBean admin = new JCSAdminBean();
+
+        List<CacheRegionInfo> regions = admin.buildCacheInfo();
+
+        boolean foundRegion = false;
+
+        for (CacheRegionInfo info : regions)
+        {
+
+            if ( info.getCacheName().equals( regionName ) )
+            {
+                foundRegion = true;
+
+                assertTrue( "Byte count should be greater than 5.", info.getByteCount() > 5 );
+
+                assertNotNull( "Should have stats.", info.getCacheStatistics() );
+            }
+        }
+
+        assertTrue( "Should have found the region we just created.", foundRegion );
+    }
+
+    /**
+     * Put a value in a region and verify that it shows up.
+     *
+     * @throws Exception
+     */
+    public void testGetElementForRegionInfo()
+        throws Exception
+    {
+        String regionName = "myRegion";
+        CacheAccess<String, String> cache = JCS.getInstance( regionName );
+
+        // clear the region
+        cache.clear();
+
+        String key = "myKey";
+        cache.put( key, "value" );
+
+        JCSAdminBean admin = new JCSAdminBean();
+
+        List<CacheElementInfo> elements = admin.buildElementInfo( regionName );
+        assertEquals( "Wrong number of elements in the region.", 1, elements.size() );
+
+        CacheElementInfo elementInfo = elements.get(0);
+        assertEquals( "Wrong key." + elementInfo, key, elementInfo.getKey() );
+    }
+
+    /**
+     * Remove an item via the remove method.
+     *
+     * @throws Exception
+     */
+    public void testRemove()
+        throws Exception
+    {
+        JCSAdminBean admin = new JCSAdminBean();
+
+        String regionName = "myRegion";
+        CacheAccess<String, String> cache = JCS.getInstance( regionName );
+
+        // clear the region
+        cache.clear();
+        admin.clearRegion( regionName );
+
+        String key = "myKey";
+        cache.put( key, "value" );
+
+        List<CacheElementInfo> elements = admin.buildElementInfo( regionName );
+        assertEquals( "Wrong number of elements in the region.", 1, elements.size() );
+
+        CacheElementInfo elementInfo = elements.get(0);
+        assertEquals( "Wrong key.", key, elementInfo.getKey() );
+
+        admin.removeItem( regionName, key );
+
+        List<CacheElementInfo> elements2 = admin.buildElementInfo( regionName );
+        assertEquals( "Wrong number of elements in the region after remove.", 0, elements2.size() );
+    }
+
+    /**
+     * Add an item to a region. Call clear all and verify that it doesn't exist.
+     *
+     * @throws Exception
+     */
+    public void testClearAll()
+        throws Exception
+    {
+        JCSAdminBean admin = new JCSAdminBean();
+
+        String regionName = "myRegion";
+        CacheAccess<String, String> cache = JCS.getInstance( regionName );
+
+        String key = "myKey";
+        cache.put( key, "value" );
+
+        admin.clearAllRegions();
+
+        List<CacheElementInfo> elements2 = admin.buildElementInfo( regionName );
+        assertEquals( "Wrong number of elements in the region after remove.", 0, elements2.size() );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/admin/CountingStreamUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/admin/CountingStreamUnitTest.java
new file mode 100644
index 0000000..ddd4722
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/admin/CountingStreamUnitTest.java
@@ -0,0 +1,79 @@
+package org.apache.commons.jcs3.admin;
+
+import org.apache.commons.jcs3.admin.CountingOnlyOutputStream;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for the counting only output stream.
+ *
+ * @author Aaron Smuts
+ *
+ */
+public class CountingStreamUnitTest
+    extends TestCase
+{
+
+    /**
+     * Write a single byte and verify the count.
+     *
+     * @throws Exception
+     */
+    public void testSingleByte() throws Exception
+    {
+        CountingOnlyOutputStream out = new CountingOnlyOutputStream();
+        out.write( 1 );
+        assertEquals( "Wrong number of bytes written.", 1, out.getCount() );
+        out.write( 1 );
+        assertEquals( "Wrong number of bytes written.", 2, out.getCount() );
+        out.close();
+    }
+
+    /**
+     * This should count the size of the array.
+     *
+     * @throws Exception
+     */
+    public void testByteArray() throws Exception
+    {
+        CountingOnlyOutputStream out = new CountingOnlyOutputStream();
+        byte[] array = new byte[]{1,2,3,4,5};
+        out.write( array );
+        assertEquals( "Wrong number of bytes written.", array.length, out.getCount() );
+        out.close();
+    }
+
+    /**
+     * This should count the len -- the third arg
+     *
+     * @throws Exception
+     */
+    public void testByteArrayLenCount() throws Exception
+    {
+        CountingOnlyOutputStream out = new CountingOnlyOutputStream();
+        byte[] array = new byte[]{1,2,3,4,5};
+        int len = 3;
+        out.write( array, 0, len );
+        assertEquals( "Wrong number of bytes written.", len, out.getCount() );
+        out.close();
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/admin/TestJMX.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/admin/TestJMX.java
new file mode 100644
index 0000000..2f1c518
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/admin/TestJMX.java
@@ -0,0 +1,38 @@
+package org.apache.commons.jcs3.admin;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+
+/**
+ * Helper class to test the JMX registration
+ */
+public class TestJMX
+{
+	public static void main(String[] args) throws Exception
+	{
+		CacheAccess<String, String> cache = JCS.getInstance("test");
+
+		cache.put("key", "value");
+        System.out.println("Waiting...");
+        Thread.sleep(Long.MAX_VALUE);
+	}
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/AuxiliaryCacheConfiguratorUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/AuxiliaryCacheConfiguratorUnitTest.java
new file mode 100644
index 0000000..cd0dee0
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/AuxiliaryCacheConfiguratorUnitTest.java
@@ -0,0 +1,131 @@
+package org.apache.commons.jcs3.auxiliary;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+import org.apache.commons.jcs3.engine.control.MockElementSerializer;
+import org.apache.commons.jcs3.engine.logging.MockCacheEventLogger;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheConfigurator;
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.utils.serialization.StandardSerializer;
+
+import java.util.Properties;
+
+/** Unit tests for the auxiliary cache configurator. */
+public class AuxiliaryCacheConfiguratorUnitTest
+    extends TestCase
+{
+    /**
+     * Verify that we don't get an error.
+     */
+    public void testParseCacheEventLogger_Null()
+    {
+        // SETUP
+        Properties props = new Properties();
+
+        // DO WORK
+        MockCacheEventLogger result = (MockCacheEventLogger) AuxiliaryCacheConfigurator.parseCacheEventLogger( props,
+                                                                                                               "junk" );
+
+        // VERIFY
+        assertNull( "Should not have a logger.", result );
+    }
+
+    /**
+     * Verify that we don't get an error.
+     */
+    public void testParseCacheEventLogger_NullName()
+    {
+        // SETUP
+        Properties props = new Properties();
+
+        // DO WORK
+        MockCacheEventLogger result = (MockCacheEventLogger) AuxiliaryCacheConfigurator.parseCacheEventLogger( props,
+                                                                                                               null );
+
+        // VERIFY
+        assertNull( "Should not have a logger.", result );
+    }
+
+    /**
+     * Verify that we can parse the event logger.
+     */
+    public void testParseCacheEventLogger_Normal()
+    {
+        // SETUP
+        String auxPrefix = "jcs.auxiliary." + "MYAux";
+        String testPropertyValue = "This is the value";
+        String className = MockCacheEventLogger.class.getName();
+
+        Properties props = new Properties();
+        props.put( auxPrefix + AuxiliaryCacheConfigurator.CACHE_EVENT_LOGGER_PREFIX, className );
+        props.put( auxPrefix + AuxiliaryCacheConfigurator.CACHE_EVENT_LOGGER_PREFIX
+            + AuxiliaryCacheConfigurator.ATTRIBUTE_PREFIX + ".testProperty", testPropertyValue );
+
+        // DO WORK
+        MockCacheEventLogger result = (MockCacheEventLogger) AuxiliaryCacheConfigurator
+            .parseCacheEventLogger( props, auxPrefix );
+
+        // VERIFY
+        assertNotNull( "Should have a logger.", result );
+        assertEquals( "Property should be set.", testPropertyValue, result.getTestProperty() );
+    }
+
+    /**
+     * Verify that we can parse the ElementSerializer.
+     */
+    public void testParseElementSerializer_Normal()
+    {
+        // SETUP
+        String auxPrefix = "jcs.auxiliary." + "MYAux";
+        String testPropertyValue = "This is the value";
+        String className = MockElementSerializer.class.getName();
+
+        Properties props = new Properties();
+        props.put( auxPrefix + AuxiliaryCacheConfigurator.SERIALIZER_PREFIX, className );
+        props.put( auxPrefix + AuxiliaryCacheConfigurator.SERIALIZER_PREFIX
+            + AuxiliaryCacheConfigurator.ATTRIBUTE_PREFIX + ".testProperty", testPropertyValue );
+
+        // DO WORK
+        MockElementSerializer result = (MockElementSerializer) AuxiliaryCacheConfigurator
+            .parseElementSerializer( props, auxPrefix );
+
+        // VERIFY
+        assertNotNull( "Should have a Serializer.", result );
+        assertEquals( "Property should be set.", testPropertyValue, result.getTestProperty() );
+    }
+
+    /**
+     * Verify that we can parse the ElementSerializer.
+     */
+    public void testParseElementSerializer_Null()
+    {
+        // SETUP
+        Properties props = new Properties();
+
+        // DO WORK
+        IElementSerializer result = AuxiliaryCacheConfigurator
+            .parseElementSerializer( props, "junk" );
+
+        // VERIFY
+        assertTrue( "Should have the default Serializer.", result instanceof StandardSerializer );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/MockAuxiliaryCache.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/MockAuxiliaryCache.java
new file mode 100644
index 0000000..73faea3
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/MockAuxiliaryCache.java
@@ -0,0 +1,217 @@
+package org.apache.commons.jcs3.auxiliary;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCache;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
+import org.apache.commons.jcs3.engine.CacheStatus;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.stats.behavior.IStats;
+
+/**
+ * Mock auxiliary for unit tests.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class MockAuxiliaryCache<K, V>
+    extends AbstractAuxiliaryCache<K, V>
+{
+    /** Can setup the cache type */
+    public CacheType cacheType = CacheType.DISK_CACHE;
+
+    /** Can setup status */
+    public CacheStatus status = CacheStatus.ALIVE;
+
+    /** Times getMatching was Called */
+    public int getMatchingCallCount = 0;
+
+    /**
+     * @param ce
+     * @throws IOException
+     */
+    @Override
+    public void update( ICacheElement<K, V> ce )
+        throws IOException
+    {
+        // TODO Auto-generated method stub
+
+    }
+
+    /**
+     * @param key
+     * @return ICacheElement
+     * @throws IOException
+     */
+    @Override
+    public ICacheElement<K, V> get( K key )
+        throws IOException
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    /**
+     * @param pattern
+     * @return Map
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMatching(String pattern)
+        throws IOException
+    {
+        getMatchingCallCount++;
+        return new HashMap<>();
+    }
+
+    /**
+     * Gets multiple items from the cache based on the given set of keys.
+     * <p>
+     * @param keys
+     * @return a map of K key to ICacheElement&lt;String, String&gt; element, or an empty map if there is no
+     *         data in cache for any of these keys
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMultiple(Set<K> keys)
+    {
+        return new HashMap<>();
+    }
+
+    /**
+     * @param key
+     * @return boolean
+     * @throws IOException
+     */
+    @Override
+    public boolean remove( K key )
+        throws IOException
+    {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+    /**
+     * @throws IOException
+     */
+    @Override
+    public void removeAll()
+        throws IOException
+    {
+        // TODO Auto-generated method stub
+
+    }
+
+    /**
+     * @throws IOException
+     */
+    @Override
+    public void dispose()
+        throws IOException
+    {
+        // TODO Auto-generated method stub
+
+    }
+
+    /**
+     * @return int
+     */
+    @Override
+    public int getSize()
+    {
+        // TODO Auto-generated method stub
+        return 0;
+    }
+
+    /**
+     * @return int
+     */
+    @Override
+    public CacheStatus getStatus()
+    {
+        return status;
+    }
+
+    /**
+     * @return null
+     */
+    @Override
+    public String getCacheName()
+    {
+        return null;
+    }
+
+    /**
+     * Return the keys in this cache.
+     * <p>
+     * @see org.apache.commons.jcs3.auxiliary.disk.AbstractDiskCache#getKeySet()
+     */
+    @Override
+    public Set<K> getKeySet() throws IOException
+    {
+        return null;
+    }
+
+    /**
+     * @return null
+     */
+    @Override
+    public IStats getStatistics()
+    {
+        return null;
+    }
+
+    /**
+     * @return null
+     */
+    @Override
+    public String getStats()
+    {
+        return null;
+    }
+
+    /**
+     * @return cacheType
+     */
+    @Override
+    public CacheType getCacheType()
+    {
+        return cacheType;
+    }
+
+    /**
+     * @return Returns the AuxiliaryCacheAttributes.
+     */
+    @Override
+    public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
+    {
+        return null;
+    }
+
+    /** @return null */
+    @Override
+    public String getEventLoggingExtraInfo()
+    {
+        return null;
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/MockAuxiliaryCacheAttributes.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/MockAuxiliaryCacheAttributes.java
new file mode 100644
index 0000000..5eaa7fe
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/MockAuxiliaryCacheAttributes.java
@@ -0,0 +1,31 @@
+package org.apache.commons.jcs3.auxiliary;
+
+import org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCacheAttributes;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/** For testing. */
+public class MockAuxiliaryCacheAttributes
+    extends AbstractAuxiliaryCacheAttributes
+{
+    /** Don't change. */
+    private static final long serialVersionUID = 1091238902450504108L;
+
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/MockAuxiliaryCacheFactory.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/MockAuxiliaryCacheFactory.java
new file mode 100644
index 0000000..dc79ffb
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/MockAuxiliaryCacheFactory.java
@@ -0,0 +1,73 @@
+package org.apache.commons.jcs3.auxiliary;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCacheFactory;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCache;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+
+/** For testing */
+public class MockAuxiliaryCacheFactory
+    extends AbstractAuxiliaryCacheFactory
+{
+    /** the name of the aux */
+    public String name = "MockAuxiliaryCacheFactory";
+
+    /**
+     * Creates a mock aux.
+     * <p>
+     * @param attr
+     * @param cacheMgr
+     * @param cacheEventLogger
+     * @param elementSerializer
+     * @return AuxiliaryCache
+     */
+    @Override
+    public <K, V> AuxiliaryCache<K, V>
+        createCache( AuxiliaryCacheAttributes attr, ICompositeCacheManager cacheMgr,
+           ICacheEventLogger cacheEventLogger, IElementSerializer elementSerializer )
+    {
+        MockAuxiliaryCache<K, V> auxCache = new MockAuxiliaryCache<>();
+        auxCache.setCacheEventLogger( cacheEventLogger );
+        auxCache.setElementSerializer( elementSerializer );
+        return auxCache;
+    }
+
+    /**
+     * @return String
+     */
+    @Override
+    public String getName()
+    {
+        return name;
+    }
+
+    /**
+     * @param s
+     */
+    @Override
+    public void setName( String s )
+    {
+        this.name = s;
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/MockCacheEventLogger.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/MockCacheEventLogger.java
new file mode 100644
index 0000000..a70bd28
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/MockCacheEventLogger.java
@@ -0,0 +1,98 @@
+package org.apache.commons.jcs3.auxiliary;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.jcs3.engine.logging.CacheEvent;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEvent;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+
+/**
+ * For testing auxiliary event logging. Improve later so we can test the details. This is very
+ * crude.
+ */
+public class MockCacheEventLogger
+    implements ICacheEventLogger
+{
+    /** times called */
+    public int applicationEventCalls = 0;
+
+    /** times called */
+    public int startICacheEventCalls = 0;
+
+    /** times called */
+    public int endICacheEventCalls = 0;
+
+    /** times called */
+    public int errorEventCalls = 0;
+
+    /** list of messages */
+    public List<String> errorMessages = new ArrayList<>();
+
+    /**
+     * @param source
+     * @param eventName
+     * @param optionalDetails
+     */
+    @Override
+    public void logApplicationEvent( String source, String eventName, String optionalDetails )
+    {
+        applicationEventCalls++;
+    }
+
+    /**
+     * @param event
+     */
+    @Override
+    public <T> void logICacheEvent( ICacheEvent<T> event )
+    {
+        endICacheEventCalls++;
+    }
+
+    /**
+     * @param source
+     * @param eventName
+     * @param errorMessage
+     */
+    @Override
+    public void logError( String source, String eventName, String errorMessage )
+    {
+        errorEventCalls++;
+        errorMessages.add( errorMessage );
+    }
+
+    /**
+     * @param source
+     * @param region
+     * @param eventName
+     * @param optionalDetails
+     * @param key
+     * @return ICacheEvent
+     */
+    @Override
+    public <T> ICacheEvent<T> createICacheEvent( String source, String region,
+            String eventName, String optionalDetails, T key )
+    {
+        startICacheEventCalls++;
+        return new CacheEvent<>();
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/AbstractDiskCacheUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/AbstractDiskCacheUnitTest.java
new file mode 100644
index 0000000..1ec3488
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/AbstractDiskCacheUnitTest.java
@@ -0,0 +1,302 @@
+package org.apache.commons.jcs3.auxiliary.disk;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.jcs3.TestLogConfigurationUtil;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.disk.AbstractDiskCache;
+import org.apache.commons.jcs3.auxiliary.disk.behavior.IDiskCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes;
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.CacheStatus;
+import org.apache.commons.jcs3.engine.ElementAttributes;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+
+/** Tests for the abstract disk cache. It's largely tested by actual instances. */
+public class AbstractDiskCacheUnitTest
+    extends TestCase
+{
+    /**
+     * Verify that update and get work.
+     * <p>
+     * @throws IOException
+     */
+    public void testUpdateGet_allowed()
+        throws IOException
+    {
+        // SETUP
+        String cacheName = "testUpdateGet_allowed";
+        IDiskCacheAttributes diskCacheAttributes = new IndexedDiskCacheAttributes();
+        diskCacheAttributes.setCacheName( cacheName );
+
+        AbstractDiskCacheTestInstance<String, String> diskCache = new AbstractDiskCacheTestInstance<>( diskCacheAttributes );
+
+        String key = "myKey";
+        String value = "myValue";
+        IElementAttributes elementAttributes = new ElementAttributes();
+        ICacheElement<String, String> cacheElement = new CacheElement<>( cacheName, key, value, elementAttributes );
+
+        diskCache.update( cacheElement );
+
+        // DO WORK
+        ICacheElement<String, String> result = diskCache.get( key );
+
+        // VERIFY
+        //System.out.println( diskCache.getStats() );
+        assertNotNull( "Item should be in the map.", result );
+    }
+
+    /**
+     * Verify that alive is set to false..
+     * <p>
+     * @throws IOException
+     */
+    public void testDispose()
+        throws IOException
+    {
+        // SETUP
+        String cacheName = "testDispose";
+        IDiskCacheAttributes diskCacheAttributes = new IndexedDiskCacheAttributes();
+        diskCacheAttributes.setCacheName( cacheName );
+
+        AbstractDiskCacheTestInstance<String, String> diskCache = new AbstractDiskCacheTestInstance<>( diskCacheAttributes );
+
+        String key = "myKey";
+        String value = "myValue";
+        IElementAttributes elementAttributes = new ElementAttributes();
+        ICacheElement<String, String> cacheElement = new CacheElement<>( cacheName, key, value, elementAttributes );
+
+        diskCache.update( cacheElement );
+
+        // DO WORK
+        diskCache.dispose();
+
+        // VERIFY
+        assertFalse( "disk cache should not be alive.", diskCache.isAlive() );
+        assertEquals( "Status should be disposed", CacheStatus.DISPOSED, diskCache.getStatus() );
+    }
+
+    /**
+     * Verify that removeAll is prohibited.
+     * <p>
+     * @throws IOException
+     */
+    public void testRemoveAll_notAllowed()
+        throws IOException
+    {
+        // SETUP
+        StringWriter stringWriter = new StringWriter();
+        TestLogConfigurationUtil.configureLogger( stringWriter, AbstractDiskCache.class.getName() );
+
+        IDiskCacheAttributes diskCacheAttributes = new IndexedDiskCacheAttributes();
+        diskCacheAttributes.setAllowRemoveAll( false );
+
+        AbstractDiskCacheTestInstance<String, String> diskCache = new AbstractDiskCacheTestInstance<>( diskCacheAttributes );
+
+        String cacheName = "testRemoveAll_notAllowed";
+        String key = "myKey";
+        String value = "myValue";
+        IElementAttributes elementAttributes = new ElementAttributes();
+        ICacheElement<String, String> cacheElement = new CacheElement<>( cacheName, key, value, elementAttributes );
+
+        diskCache.update( cacheElement );
+
+        // DO WORK
+        diskCache.removeAll();
+        String result = stringWriter.toString();
+
+        // VERIFY
+        assertTrue( "Should say not allowed.", result.indexOf( "set to false" ) != -1 );
+        assertNotNull( "Item should be in the map.", diskCache.get( key ) );
+    }
+
+    /**
+     * Verify that removeAll is allowed.
+     * <p>
+     * @throws IOException
+     */
+    public void testRemoveAll_allowed()
+        throws IOException
+    {
+        // SETUP
+        IDiskCacheAttributes diskCacheAttributes = new IndexedDiskCacheAttributes();
+        diskCacheAttributes.setAllowRemoveAll( true );
+
+        AbstractDiskCacheTestInstance<String, String> diskCache = new AbstractDiskCacheTestInstance<>( diskCacheAttributes );
+
+        String cacheName = "testRemoveAll_allowed";
+        String key = "myKey";
+        String value = "myValue";
+        IElementAttributes elementAttributes = new ElementAttributes();
+        ICacheElement<String, String> cacheElement = new CacheElement<>( cacheName, key, value, elementAttributes );
+
+        diskCache.update( cacheElement );
+
+        // DO WORK
+        diskCache.removeAll();
+
+        // VERIFY
+        assertNull( "Item should not be in the map.", diskCache.get( key ) );
+    }
+
+    /** Concrete, testable instance. */
+    protected static class AbstractDiskCacheTestInstance<K, V>
+        extends AbstractDiskCache<K, V>
+    {
+        /** Internal map */
+        protected Map<K, ICacheElement<K, V>> map = new HashMap<>();
+
+        /** used by the abstract aux class */
+        protected IDiskCacheAttributes diskCacheAttributes;
+
+        /**
+         * Creates the disk cache.
+         * <p>
+         * @param attr
+         */
+        public AbstractDiskCacheTestInstance( IDiskCacheAttributes attr )
+        {
+            super( attr );
+            diskCacheAttributes = attr;
+            setAlive(true);
+        }
+
+        /**
+         * The location on disk
+         * <p>
+         * @return "memory"
+         */
+        @Override
+        protected String getDiskLocation()
+        {
+            return "memory";
+        }
+
+        /**
+         * Return the keys in this cache.
+         * <p>
+         * @see org.apache.commons.jcs3.auxiliary.disk.AbstractDiskCache#getKeySet()
+         */
+        @Override
+        public Set<K> getKeySet() throws IOException
+        {
+            return new HashSet<>(map.keySet());
+        }
+
+        /**
+         * @return map.size()
+         */
+        @Override
+        public int getSize()
+        {
+            return map.size();
+        }
+
+        /**
+         * @throws IOException
+         */
+        @Override
+        protected void processDispose()
+            throws IOException
+        {
+            //System.out.println( "processDispose" );
+        }
+
+        /**
+         * @param key
+         * @return ICacheElement
+         * @throws IOException
+         */
+        @Override
+        protected ICacheElement<K, V> processGet( K key )
+            throws IOException
+        {
+            //System.out.println( "processGet: " + key );
+            return map.get( key );
+        }
+
+        /**
+         * @param pattern
+         * @return Collections.EMPTY_MAP
+         * @throws IOException
+         */
+        @Override
+        protected Map<K, ICacheElement<K, V>> processGetMatching( String pattern )
+            throws IOException
+        {
+            return Collections.emptyMap();
+        }
+
+        /**
+         * @param key
+         * @return false
+         * @throws IOException
+         */
+        @Override
+        protected boolean processRemove( K key )
+            throws IOException
+        {
+            return map.remove( key ) != null;
+        }
+
+        /**
+         * @throws IOException
+         */
+        @Override
+        protected void processRemoveAll()
+            throws IOException
+        {
+            //System.out.println( "processRemoveAll" );
+            map.clear();
+        }
+
+        /**
+         * @param cacheElement
+         * @throws IOException
+         */
+        @Override
+        protected void processUpdate( ICacheElement<K, V> cacheElement )
+            throws IOException
+        {
+            //System.out.println( "processUpdate: " + cacheElement );
+            map.put( cacheElement.getKey(), cacheElement );
+        }
+
+        /**
+         * @return null
+         */
+        @Override
+        public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
+        {
+            return diskCacheAttributes;
+        }
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/DiskTestObject.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/DiskTestObject.java
new file mode 100644
index 0000000..bc97648
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/DiskTestObject.java
@@ -0,0 +1,78 @@
+package org.apache.commons.jcs3.auxiliary.disk;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+/**
+ * Resembles a cached image.
+ */
+public class DiskTestObject implements Serializable
+{
+    /** don't change */
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Key
+     */
+    public Integer id;
+
+    /**
+     * Byte size
+     */
+    public byte[] imageBytes;
+
+    /**
+     * @param id
+     * @param imageBytes
+     */
+    public DiskTestObject(Integer id, byte[] imageBytes)
+    {
+        this.id = id;
+        this.imageBytes = imageBytes;
+    }
+
+    /**
+     * @see java.lang.Object#equals(Object other)
+     */
+    @Override
+    public boolean equals(Object other)
+    {
+        if (other instanceof DiskTestObject)
+        {
+            DiskTestObject o = (DiskTestObject) other;
+            if (id != null)
+                return id.equals(o.id) && Arrays.equals(imageBytes, o.imageBytes);
+            else if (id == null && o.id == null) return Arrays.equals(imageBytes, o.imageBytes);
+        }
+        return false;
+    }
+
+    /**
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode()
+    {
+        return id.hashCode();
+    }
+
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/PurgatoryElementUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/PurgatoryElementUnitTest.java
new file mode 100644
index 0000000..adb3b8f
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/PurgatoryElementUnitTest.java
@@ -0,0 +1,92 @@
+package org.apache.commons.jcs3.auxiliary.disk;
+
+import org.apache.commons.jcs3.auxiliary.disk.PurgatoryElement;
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.ElementAttributes;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/** Simple unit tests for the Purgatory Element. */
+public class PurgatoryElementUnitTest
+    extends TestCase
+{
+    /** Verify basic data */
+    public void testSpoolable_normal()
+    {
+        // SETUP
+        String cacheName = "myCacheName";
+        String key = "myKey";
+        String value = "myValue";
+        IElementAttributes elementAttributes = new ElementAttributes();
+        ICacheElement<String, String> cacheElement = new CacheElement<>( cacheName, key, value, elementAttributes );
+        PurgatoryElement<String, String> purgatoryElement = new PurgatoryElement<>( cacheElement );
+        purgatoryElement.setSpoolable( false );
+
+        // DO WORK
+        boolean result = purgatoryElement.isSpoolable();
+
+        // VERIFY
+        assertFalse( "Should not be spoolable.", result );
+    }
+
+    /** Verify basic data */
+    public void testElementAttributes_normal()
+    {
+        // SETUP
+        String cacheName = "myCacheName";
+        String key = "myKey";
+        String value = "myValue";
+        IElementAttributes elementAttributes = new ElementAttributes();
+
+        ICacheElement<String, String> cacheElement = new CacheElement<>( cacheName, key, value );
+        PurgatoryElement<String, String> purgatoryElement = new PurgatoryElement<>( cacheElement );
+        purgatoryElement.setElementAttributes( elementAttributes );
+
+        // DO WORK
+        IElementAttributes result = cacheElement.getElementAttributes();
+
+        // VERIFY
+        assertEquals( "Should have set the attributes on the element", elementAttributes, result );
+    }
+
+    /** Verify basic data */
+    public void testToString_normal()
+    {
+        // SETUP
+        String cacheName = "myCacheName";
+        String key = "myKey";
+        String value = "myValue";
+        IElementAttributes elementAttributes = new ElementAttributes();
+        ICacheElement<String, String> cacheElement = new CacheElement<>( cacheName, key, value, elementAttributes );
+        PurgatoryElement<String, String> purgatoryElement = new PurgatoryElement<>( cacheElement );
+
+        // DO WORK
+        String result = purgatoryElement.toString();
+
+        // VERIFY
+        assertTrue( "Should have the cacheName.", result.indexOf( cacheName ) != -1 );
+        assertTrue( "Should have the key.", result.indexOf( key ) != -1 );
+        assertTrue( "Should have the value.", result.indexOf( value ) != -1 );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCacheConcurrentUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCacheConcurrentUnitTest.java
new file mode 100644
index 0000000..fc78205
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCacheConcurrentUnitTest.java
@@ -0,0 +1,259 @@
+package org.apache.commons.jcs3.auxiliary.disk.block;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.extensions.ActiveTestSuite;
+import junit.framework.Test;
+import junit.framework.TestCase;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+
+/**
+ * Test which exercises the block disk cache. This one uses three different
+ * regions for three threads.
+ */
+public class BlockDiskCacheConcurrentUnitTest
+    extends TestCase
+{
+    /**
+     * Number of items to cache, twice the configured maxObjects for the memory
+     * cache regions.
+     */
+    private static int items = 200;
+
+    /**
+     * Constructor for the TestDiskCache object.
+     *
+     * @param testName
+     * @throws Exception
+     */
+    public BlockDiskCacheConcurrentUnitTest( String testName )
+        throws Exception
+    {
+        super( testName );
+    }
+
+    /**
+     * Main method passes this test to the text test runner.
+     *
+     * @param args
+     */
+    public static void main( String args[] )
+    {
+        String[] testCaseName = { BlockDiskCacheConcurrentUnitTest.class.getName() };
+        junit.textui.TestRunner.main( testCaseName );
+    }
+
+    /**
+     * A unit test suite for JUnit
+     *
+     * @return The test suite
+     * @throws Exception
+     */
+    public static Test suite()
+        throws Exception
+    {
+        ActiveTestSuite suite = new ActiveTestSuite();
+
+        JCS.setConfigFilename( "/TestBlockDiskCache.ccf" );
+        JCS.getInstance( "indexedRegion1" ).clear();
+        JCS.getInstance( "indexedRegion2" ).clear();
+        JCS.getInstance( "indexedRegion3" ).clear();
+
+        suite.addTest( new BlockDiskCacheConcurrentUnitTest( "testBlockDiskCache1" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "indexedRegion1" );
+            }
+        } );
+
+        suite.addTest( new BlockDiskCacheConcurrentUnitTest( "testBlockDiskCache2" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "indexedRegion2" );
+            }
+        } );
+
+        suite.addTest( new BlockDiskCacheConcurrentUnitTest( "testBlockDiskCache3" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "indexedRegion3" );
+            }
+        } );
+
+        suite.addTest( new BlockDiskCacheConcurrentUnitTest( "testBlockDiskCache4" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegionInRange( "indexedRegion3", 300, 600 );
+            }
+        } );
+
+        return suite;
+    }
+
+    /**
+     * Test setup
+     */
+    @Override
+    public void setUp()
+    {
+        JCS.setConfigFilename( "/TestBlockDiskCache.ccf" );
+    }
+
+    /**
+     * Adds items to cache, gets them, and removes them. The item count is more
+     * than the size of the memory cache, so items should spool to disk.
+     *
+     * @param region
+     *            Name of the region to access
+     *
+     * @throws Exception
+     *                If an error occurs
+     */
+    public void runTestForRegion( String region )
+        throws Exception
+    {
+        CacheAccess<String, String> jcs = JCS.getInstance( region );
+
+        // Add items to cache
+        for ( int i = 0; i <= items; i++ )
+        {
+            jcs.put( i + ":key", region + " data " + i );
+        }
+
+        // Test that all items are in cache
+        for ( int i = 0; i <= items; i++ )
+        {
+            String value = jcs.get( i + ":key" );
+
+            assertEquals( region + " data " + i, value );
+        }
+
+        // Test that getElements returns all the expected values
+        Set<String> keys = new HashSet<>();
+        for ( int i = 0; i <= items; i++ )
+        {
+            keys.add( i + ":key" );
+        }
+
+        Map<String, ICacheElement<String, String>> elements = jcs.getCacheElements( keys );
+        for ( int i = 0; i <= items; i++ )
+        {
+            ICacheElement<String, String> element = elements.get( i + ":key" );
+            assertNotNull( "element " + i + ":key is missing", element );
+            assertEquals( "value " + i + ":key", region + " data " + i, element.getVal() );
+        }
+
+        // Remove all the items
+        for ( int i = 0; i <= items; i++ )
+        {
+            jcs.remove( i + ":key" );
+        }
+
+        // Verify removal
+        // another thread may have inserted since
+        for ( int i = 0; i <= items; i++ )
+        {
+            assertNull( "Removed key should be null: " + i + ":key" + "\n stats " + jcs.getStats(), jcs
+                .get( i + ":key" ) );
+        }
+    }
+
+    /**
+     * Adds items to cache, gets them, and removes them. The item count is more
+     * than the size of the memory cache, so items should spool to disk.
+     *
+     * @param region
+     *            Name of the region to access
+     * @param start
+     * @param end
+     *
+     * @throws Exception
+     *                If an error occurs
+     */
+    public void runTestForRegionInRange( String region, int start, int end )
+        throws Exception
+    {
+        CacheAccess<String, String> jcs = JCS.getInstance( region );
+
+        // Add items to cache
+        for ( int i = start; i <= end; i++ )
+        {
+            jcs.put( i + ":key", region + " data " + i );
+        }
+
+        // Test that all items are in cache
+        for ( int i = start; i <= end; i++ )
+        {
+            String value = jcs.get( i + ":key" );
+
+            assertEquals( region + " data " + i, value );
+        }
+
+        // Test that getElements returns all the expected values
+        Set<String> keys = new HashSet<>();
+        for ( int i = start; i <= end; i++ )
+        {
+            keys.add( i + ":key" );
+        }
+
+        Map<String, ICacheElement<String, String>> elements = jcs.getCacheElements( keys );
+        for ( int i = start; i <= end; i++ )
+        {
+            ICacheElement<String, String> element = elements.get( i + ":key" );
+            assertNotNull( "element " + i + ":key is missing", element );
+            assertEquals( "value " + i + ":key", region + " data " + i, element.getVal() );
+        }
+
+        // Remove all the items
+        for ( int i = start; i <= end; i++ )
+        {
+            jcs.remove( i + ":key" );
+        }
+
+//        System.out.println( jcs.getStats() );
+
+        // Verify removal
+        // another thread may have inserted since
+        for ( int i = start; i <= end; i++ )
+        {
+            assertNull( "Removed key should be null: " + i + ":key " + "\n stats " + jcs.getStats(), jcs.get( i
+                + ":key" ) );
+        }
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCacheCountUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCacheCountUnitTest.java
new file mode 100644
index 0000000..df8f109
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCacheCountUnitTest.java
@@ -0,0 +1,36 @@
+package org.apache.commons.jcs3.auxiliary.disk.block;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.auxiliary.disk.behavior.IDiskCacheAttributes.DiskLimitType;

+import org.apache.commons.jcs3.auxiliary.disk.block.BlockDiskCacheAttributes;

+

+public class BlockDiskCacheCountUnitTest extends BlockDiskCacheUnitTestAbstract

+{

+

+    @Override

+    public BlockDiskCacheAttributes getCacheAttributes()

+    {

+        BlockDiskCacheAttributes ret = new BlockDiskCacheAttributes();

+        ret.setDiskLimitType(DiskLimitType.COUNT);

+        return ret;

+    }

+

+}

diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCacheKeyStoreUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCacheKeyStoreUnitTest.java
new file mode 100644
index 0000000..8c190df
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCacheKeyStoreUnitTest.java
@@ -0,0 +1,193 @@
+package org.apache.commons.jcs3.auxiliary.disk.block;
+
+import org.apache.commons.jcs3.auxiliary.disk.behavior.IDiskCacheAttributes.DiskLimitType;
+import org.apache.commons.jcs3.auxiliary.disk.block.BlockDiskCache;
+import org.apache.commons.jcs3.auxiliary.disk.block.BlockDiskCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.disk.block.BlockDiskKeyStore;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for the keyStore.
+ * <p>
+ *
+ * @author Aaron Smuts
+ */
+public class BlockDiskCacheKeyStoreUnitTest
+        extends TestCase
+{
+    /** Directory name */
+    private final String rootDirName = "target/test-sandbox/block";
+
+    /**
+     * Put a bunch of keys in the key store and verify that they are present.
+     * <p>
+     *
+     * @throws Exception
+     */
+    public void testPutKeys()
+            throws Exception
+    {
+        // SETUP
+        BlockDiskCacheAttributes attributes = new BlockDiskCacheAttributes();
+        attributes.setCacheName("testPutKeys");
+        attributes.setDiskPath(rootDirName);
+        attributes.setMaxKeySize(1000);
+        attributes.setBlockSizeBytes(2000);
+
+        innerTestPutKeys(attributes);
+    }
+
+    public void testPutKeysSize()
+            throws Exception
+    {
+        // SETUP
+        BlockDiskCacheAttributes attributes = new BlockDiskCacheAttributes();
+        attributes.setCacheName("testPutKeys");
+        attributes.setDiskPath(rootDirName);
+        attributes.setMaxKeySize(100000);
+        attributes.setBlockSizeBytes(1024);
+        attributes.setDiskLimitType(DiskLimitType.SIZE);
+
+        innerTestPutKeys(attributes);
+    }
+
+    private void innerTestPutKeys(BlockDiskCacheAttributes attributes)
+    {
+        BlockDiskCache<String, String> blockDiskCache = new BlockDiskCache<>(attributes);
+        BlockDiskKeyStore<String> keyStore = new BlockDiskKeyStore<>(attributes, blockDiskCache);
+
+        // DO WORK
+        int numElements = 100;
+        for (int i = 0; i < numElements; i++)
+        {
+            keyStore.put(String.valueOf(i), new int[i]);
+        }
+        // System.out.println( "testPutKeys " + keyStore );
+
+        // VERIFY
+        assertEquals("Wrong number of keys", numElements, keyStore.size());
+        for (int i = 0; i < numElements; i++)
+        {
+            int[] result = keyStore.get(String.valueOf(i));
+            assertEquals("Wrong array returned.", i, result.length);
+        }
+    }
+
+    /**
+     * Verify that we can load keys that we saved. Add a bunch. Save them. Clear
+     * the memory key hash. Load the keys. Verify.
+     * <p>
+     *
+     * @throws Exception
+     */
+    public void testSaveLoadKeys()
+            throws Exception
+    {
+        // SETUP
+        BlockDiskCacheAttributes attributes = new BlockDiskCacheAttributes();
+        attributes.setCacheName("testSaveLoadKeys");
+        attributes.setDiskPath(rootDirName);
+        attributes.setMaxKeySize(10000);
+        attributes.setBlockSizeBytes(2000);
+
+        testSaveLoadKeysInner(attributes);
+    }
+
+    public void testSaveLoadKeysSize()
+            throws Exception
+    {
+        // SETUP
+        BlockDiskCacheAttributes attributes = new BlockDiskCacheAttributes();
+        attributes.setCacheName("testSaveLoadKeys");
+        attributes.setDiskPath(rootDirName);
+        attributes.setMaxKeySize(10000);
+        attributes.setBlockSizeBytes(2000);
+
+        testSaveLoadKeysInner(attributes);
+    }
+
+    private void testSaveLoadKeysInner(BlockDiskCacheAttributes attributes)
+    {
+        BlockDiskKeyStore<String> keyStore = new BlockDiskKeyStore<>(attributes, null);
+
+        // DO WORK
+        int numElements = 1000;
+        int blockIndex = 0;
+        // Random random = new Random( 89 );
+        for (int i = 0; i < numElements; i++)
+        {
+            int blocks = i;// random.nextInt( 10 );
+
+            // fill with reasonable data to make verify() happy
+            int[] block1 = new int[blocks];
+            int[] block2 = new int[blocks];
+            for (int j = 0; j < blocks; j++)
+            {
+                block1[j] = blockIndex++;
+                block2[j] = blockIndex++;
+            }
+            keyStore.put(String.valueOf(i), block1);
+            keyStore.put(String.valueOf(i), block2);
+        }
+        // System.out.println( "testSaveLoadKeys " + keyStore );
+
+        // VERIFY
+        assertEquals("Wrong number of keys", numElements, keyStore.size());
+
+        // DO WORK
+        keyStore.saveKeys();
+        keyStore.clearMemoryMap();
+
+        // VERIFY
+        assertEquals("Wrong number of keys after clearing memory", 0, keyStore.size());
+
+        // DO WORK
+        keyStore.loadKeys();
+
+        // VERIFY
+        assertEquals("Wrong number of keys after loading", numElements, keyStore.size());
+        for (int i = 0; i < numElements; i++)
+        {
+            int[] result = keyStore.get(String.valueOf(i));
+            assertEquals("Wrong array returned.", i, result.length);
+        }
+    }
+
+    public void testObjectLargerThanMaxSize()
+    {
+        BlockDiskCacheAttributes attributes = new BlockDiskCacheAttributes();
+        attributes.setCacheName("testObjectLargerThanMaxSize");
+        attributes.setDiskPath(rootDirName);
+        attributes.setMaxKeySize(1000);
+        attributes.setBlockSizeBytes(2000);
+        attributes.setDiskLimitType(DiskLimitType.SIZE);
+
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        BlockDiskKeyStore<String> keyStore = new BlockDiskKeyStore<>(attributes, new BlockDiskCache(attributes));
+
+        keyStore.put("1", new int[1000]);
+        keyStore.put("2", new int[1000]);
+        assertNull(keyStore.get("1"));
+        assertNotNull(keyStore.get("2"));
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCacheRandomConcurrentTestUtil.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCacheRandomConcurrentTestUtil.java
new file mode 100644
index 0000000..3d7f1ef
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCacheRandomConcurrentTestUtil.java
@@ -0,0 +1,91 @@
+package org.apache.commons.jcs3.auxiliary.disk.block;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+import org.apache.commons.jcs3.access.TestCacheAccess;
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.junit.Test;
+
+/**
+ * This is used by other tests to generate a random load on the disk cache.
+ */
+public class BlockDiskCacheRandomConcurrentTestUtil
+    extends TestCase
+{
+    /**
+     * Constructor for the TestDiskCache object.
+     *
+     * @param testName
+     */
+    public BlockDiskCacheRandomConcurrentTestUtil( String testName )
+    {
+        super( testName );
+    }
+
+    @Test
+    public void test()
+    {
+
+    }
+
+    /**
+     * Randomly adds items to cache, gets them, and removes them. The range
+     * count is more than the size of the memory cache, so items should spool to
+     * disk.
+     * <p>
+     * @param region
+     *            Name of the region to access
+     * @param range
+     * @param numOps
+     * @param testNum
+     *
+     * @throws Exception
+     *                If an error occurs
+     */
+    public void runTestForRegion( String region, int range, int numOps, int testNum )
+        throws Exception
+    {
+        // run a rondom operation test to detect deadlocks
+        TestCacheAccess tca = new TestCacheAccess( "/TestBlockDiskCacheCon.ccf" );
+        tca.setRegion( region );
+        tca.random( range, numOps );
+
+        // make sure a simple put then get works
+        // this may fail if the other tests are flooding the disk cache
+        CacheAccess<String, String> jcs = JCS.getInstance( region );
+        String key = "testKey" + testNum;
+        String data = "testData" + testNum;
+        jcs.put( key, data );
+        String value = jcs.get( key );
+        assertEquals( data, value );
+    }
+
+    /**
+     * Test setup
+     */
+    @Override
+    public void setUp()
+    {
+        JCS.setConfigFilename( "/TestBlockDiskCacheCon.ccf" );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCacheSameRegionConcurrentUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCacheSameRegionConcurrentUnitTest.java
new file mode 100644
index 0000000..ee79176
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCacheSameRegionConcurrentUnitTest.java
@@ -0,0 +1,173 @@
+package org.apache.commons.jcs3.auxiliary.disk.block;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.extensions.ActiveTestSuite;
+import junit.framework.Test;
+import junit.framework.TestCase;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+
+/**
+ * Test which exercises the block disk cache. Runs three threads against the same region.
+ */
+public class BlockDiskCacheSameRegionConcurrentUnitTest
+    extends TestCase
+{
+    /**
+     * Constructor for the TestDiskCache object.
+     * <p>
+     * @param testName
+     */
+    public BlockDiskCacheSameRegionConcurrentUnitTest( String testName )
+    {
+        super( testName );
+    }
+
+    /**
+     * Main method passes this test to the text test runner.
+     * <p>
+     * @param args
+     * @throws InterruptedException
+     */
+    public static void main( String args[] ) throws InterruptedException
+    {
+        String[] testCaseName = { BlockDiskCacheSameRegionConcurrentUnitTest.class.getName() };
+        junit.textui.TestRunner.main( testCaseName );
+
+        // Give test threads some time to finish
+        Thread.sleep(2000);
+    }
+
+    /**
+     * A unit test suite for JUnit
+     * @return The test suite
+     */
+    public static Test suite()
+    {
+        ActiveTestSuite suite = new ActiveTestSuite();
+
+        suite.addTest( new BlockDiskCacheSameRegionConcurrentUnitTest( "testBlockDiskCache1" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "blockRegion4", 0, 200 );
+            }
+        } );
+
+        suite.addTest( new BlockDiskCacheSameRegionConcurrentUnitTest( "testBlockDiskCache2" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "blockRegion4", 1000, 1200 );
+            }
+        } );
+
+        suite.addTest( new BlockDiskCacheSameRegionConcurrentUnitTest( "testBlockDiskCache3" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "blockRegion4", 2000, 2200 );
+            }
+        } );
+
+        suite.addTest( new BlockDiskCacheSameRegionConcurrentUnitTest( "testBlockDiskCache4" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "blockRegion4", 2200, 5200 );
+            }
+        } );
+
+        return suite;
+    }
+
+    /**
+     * Test setup.  Sets the config name and clears the region.
+     * <p>
+     * @throws Exception
+     */
+    @Override
+    public void setUp()
+        throws Exception
+    {
+        JCS.setConfigFilename( "/TestBlockDiskCacheCon.ccf" );
+    }
+
+    /**
+     * Adds items to cache, gets them, and removes them. The item count is more than the size of the
+     * memory cache, so items should spool to disk.
+     * @param region Name of the region to access
+     * @param start
+     * @param end
+     * @throws Exception If an error occurs
+     */
+    public void runTestForRegion( String region, int start, int end )
+        throws Exception
+    {
+        CacheAccess<String, String> jcs = JCS.getInstance( region );
+
+        // Add items to cache
+
+        for ( int i = start; i <= end; i++ )
+        {
+            jcs.put( i + ":key", region + " data " + i + "-" + region );
+        }
+
+        // Test that all items are in cache
+
+        for ( int i = start; i <= end; i++ )
+        {
+            String key = i + ":key";
+            String value = jcs.get( key );
+
+            assertEquals( "Wrong value for key [" + key + "]", region + " data " + i + "-" + region, value );
+        }
+
+        // Test that getElements returns all the expected values
+        Set<String> keys = new HashSet<>();
+        for ( int i = start; i <= end; i++ )
+        {
+            keys.add( i + ":key" );
+        }
+
+        Map<String, ICacheElement<String, String>> elements = jcs.getCacheElements( keys );
+        for ( int i = start; i <= end; i++ )
+        {
+            ICacheElement<String, String> element = elements.get( i + ":key" );
+            assertNotNull( "element " + i + ":key is missing", element );
+            assertEquals( "value " + i + ":key", region + " data " + i + "-" + region, element.getVal() );
+        }
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCacheSizeUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCacheSizeUnitTest.java
new file mode 100644
index 0000000..04f3fe7
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCacheSizeUnitTest.java
@@ -0,0 +1,36 @@
+package org.apache.commons.jcs3.auxiliary.disk.block;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.auxiliary.disk.behavior.IDiskCacheAttributes.DiskLimitType;

+import org.apache.commons.jcs3.auxiliary.disk.block.BlockDiskCacheAttributes;

+

+public class BlockDiskCacheSizeUnitTest extends BlockDiskCacheUnitTestAbstract

+{

+

+    @Override

+    public BlockDiskCacheAttributes getCacheAttributes()

+    {

+        BlockDiskCacheAttributes ret = new BlockDiskCacheAttributes();

+        ret.setDiskLimitType(DiskLimitType.SIZE);

+        return ret;

+    }

+

+}

diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCacheSteadyLoadTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCacheSteadyLoadTest.java
new file mode 100644
index 0000000..7c10dd4
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCacheSteadyLoadTest.java
@@ -0,0 +1,161 @@
+package org.apache.commons.jcs3.auxiliary.disk.block;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.text.DecimalFormat;
+import java.util.Random;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.jcs3.auxiliary.disk.DiskTestObject;
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+
+/**
+ * This allows you to put thousands of large objects into the disk cache and to force removes to
+ * trigger optimizations along the way.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class BlockDiskCacheSteadyLoadTest
+    extends TestCase
+{
+    /** String for separating log entries. */
+    private static final String LOG_DIVIDER = "---------------------------";
+
+    /** the runtime. */
+    private static Runtime rt = Runtime.getRuntime();
+
+    /** The decimal format to use int he logs. */
+    private static DecimalFormat format = new DecimalFormat( "#,###" );
+
+    /**
+     * Insert 2000 wait 1 second, repeat. Average 1000 / sec.
+     * <p>
+     * @throws Exception
+     */
+    public void testRunSteadyLoadTest()
+        throws Exception
+    {
+        JCS.setConfigFilename( "/TestBlockDiskCacheSteadyLoad.ccf" );
+
+        logMemoryUsage();
+
+        int numPerRun = 250;
+        long pauseBetweenRuns = 1000;
+        int runCount = 0;
+        int runs = 1000;
+        int upperKB = 50;
+
+        CacheAccess<String, DiskTestObject> jcs = JCS.getInstance( ( numPerRun / 2 ) + "aSecond" );
+
+//        ElapsedTimer timer = new ElapsedTimer();
+        int numToGet = numPerRun * ( runs / 10 );
+        for ( int i = 0; i < numToGet; i++ )
+        {
+            jcs.get( String.valueOf( i ) );
+        }
+//        System.out.println( LOG_DIVIDER );
+//        System.out.println( "After getting " + numToGet );
+//        System.out.println( "Elapsed " + timer.getElapsedTimeString() );
+        logMemoryUsage();
+
+        jcs.clear();
+        Thread.sleep( 3000 );
+//        System.out.println( LOG_DIVIDER );
+//        System.out.println( "Start putting" );
+
+//        long totalSize = 0;
+        int totalPut = 0;
+
+        Random random = new Random( 89 );
+        while ( runCount < runs )
+        {
+            runCount++;
+            for ( int i = 0; i < numPerRun; i++ )
+            {
+                // 1/2 upper to upperKB-4 KB
+                int kiloBytes = Math.max( upperKB / 2, random.nextInt( upperKB ) );
+                int bytes = ( kiloBytes ) * 1024;
+//                totalSize += bytes;
+                totalPut++;
+                DiskTestObject object = new DiskTestObject( Integer.valueOf( i ), new byte[bytes] );
+                jcs.put( String.valueOf( totalPut ), object );
+            }
+
+            // get half of those inserted the previous run
+            if ( runCount > 1 )
+            {
+                for ( int j = ( ( totalPut - numPerRun ) - ( numPerRun / 2 ) ); j < ( totalPut - numPerRun ); j++ )
+                {
+                    jcs.get( String.valueOf( j ) );
+                }
+            }
+
+            // remove half of those inserted the previous run
+            if ( runCount > 1 )
+            {
+                for ( int j = ( ( totalPut - numPerRun ) - ( numPerRun / 2 ) ); j < ( totalPut - numPerRun ); j++ )
+                {
+                    jcs.remove( String.valueOf( j ) );
+                }
+            }
+
+
+            Thread.sleep( pauseBetweenRuns );
+            if ( runCount % 100 == 0 )
+            {
+//                System.out.println( LOG_DIVIDER );
+//                System.out.println( "Elapsed " + timer.getElapsedTimeString() );
+//                System.out.println( "Run count: " + runCount + " Average size: " + ( totalSize / totalPut ) + "\n"
+//                    + jcs.getStats() );
+                logMemoryUsage();
+            }
+        }
+
+        Thread.sleep( 3000 );
+//        System.out.println( jcs.getStats() );
+        logMemoryUsage();
+
+        Thread.sleep( 10000 );
+//        System.out.println( jcs.getStats() );
+        logMemoryUsage();
+
+        System.gc();
+        Thread.sleep( 3000 );
+        System.gc();
+//        System.out.println( jcs.getStats() );
+        logMemoryUsage();
+    }
+
+    /**
+     * Logs the memory usage.
+     */
+    private static void logMemoryUsage()
+    {
+        long byte2MB = 1024 * 1024;
+        long total = rt.totalMemory() / byte2MB;
+        long free = rt.freeMemory() / byte2MB;
+        long used = total - free;
+        System.out.println( LOG_DIVIDER );
+        System.out.println( "Memory:" + " Used:" + format.format( used ) + "MB" + " Free:" + format.format( free )
+            + "MB" + " Total:" + format.format( total ) + "MB" );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCacheUnitTestAbstract.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCacheUnitTestAbstract.java
new file mode 100644
index 0000000..4c07f6c
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskCacheUnitTestAbstract.java
@@ -0,0 +1,574 @@
+package org.apache.commons.jcs3.auxiliary.disk.block;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Serializable;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+
+import org.apache.commons.jcs3.auxiliary.disk.block.BlockDisk;
+import org.apache.commons.jcs3.auxiliary.disk.block.BlockDiskCache;
+import org.apache.commons.jcs3.auxiliary.disk.block.BlockDiskCacheAttributes;
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.ElementAttributes;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+import org.apache.commons.jcs3.engine.control.group.GroupAttrName;
+import org.apache.commons.jcs3.engine.control.group.GroupId;
+import org.apache.commons.jcs3.utils.serialization.StandardSerializer;
+
+import junit.framework.TestCase;
+
+/** Unit tests for the Block Disk Cache */
+public abstract class BlockDiskCacheUnitTestAbstract extends TestCase
+{
+    public abstract BlockDiskCacheAttributes getCacheAttributes();
+
+    public void testPutGetMatching_SmallWait() throws Exception
+    {
+        // SETUP
+        int items = 200;
+
+        String cacheName = "testPutGetMatching_SmallWait";
+        BlockDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName(cacheName);
+        cattr.setMaxKeySize(100);
+        cattr.setDiskPath("target/test-sandbox/BlockDiskCacheUnitTest");
+        BlockDiskCache<String, String> diskCache = new BlockDiskCache<>(cattr);
+
+        // DO WORK
+        for (int i = 0; i <= items; i++)
+        {
+            diskCache.update(new CacheElement<>(cacheName, i + ":key", cacheName + " data " + i));
+        }
+        Thread.sleep(500);
+
+        Map<String, ICacheElement<String, String>> matchingResults = diskCache.getMatching("1.8.+");
+
+        // VERIFY
+        assertEquals("Wrong number returned", 10, matchingResults.size());
+        // System.out.println( "matchingResults.keySet() " + matchingResults.keySet() );
+        // System.out.println( "\nAFTER TEST \n" + diskCache.getStats() );
+    }
+
+    /**
+     * Test the basic get matching. With no wait this will all come from purgatory.
+     * <p>
+     *
+     * @throws Exception
+     */
+    public void testPutGetMatching_NoWait() throws Exception
+    {
+        // SETUP
+        int items = 200;
+
+        String cacheName = "testPutGetMatching_NoWait";
+        BlockDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName(cacheName);
+        cattr.setMaxKeySize(100);
+        cattr.setDiskPath("target/test-sandbox/BlockDiskCacheUnitTest");
+        BlockDiskCache<String, String> diskCache = new BlockDiskCache<>(cattr);
+
+        // DO WORK
+        for (int i = 0; i <= items; i++)
+        {
+            diskCache.update(new CacheElement<>(cacheName, i + ":key", cacheName + " data " + i));
+        }
+
+        Map<String, ICacheElement<String, String>> matchingResults = diskCache.getMatching("1.8.+");
+
+        // VERIFY
+        assertEquals("Wrong number returned", 10, matchingResults.size());
+        // System.out.println( "matchingResults.keySet() " + matchingResults.keySet() );
+        // System.out.println( "\nAFTER TEST \n" + diskCache.getStats() );
+    }
+
+    /**
+     * Verify that the block disk cache can handle a big string.
+     * <p>
+     *
+     * @throws Exception
+     */
+    public void testChunk_BigString() throws Exception
+    {
+        String string = "This is my big string ABCDEFGH";
+        StringBuilder sb = new StringBuilder();
+        sb.append(string);
+        for (int i = 0; i < 4; i++)
+        {
+            sb.append("|" + i + ":" + sb.toString()); // big string
+        }
+        string = sb.toString();
+
+        StandardSerializer elementSerializer = new StandardSerializer();
+        byte[] data = elementSerializer.serialize(string);
+
+        File file = new File("target/test-sandbox/BlockDiskCacheUnitTest/testChunk_BigString.data");
+
+        BlockDisk blockDisk = new BlockDisk(file, 200, elementSerializer);
+
+        int numBlocksNeeded = blockDisk.calculateTheNumberOfBlocksNeeded(data);
+        // System.out.println( numBlocksNeeded );
+
+        // get the individual sub arrays.
+        byte[][] chunks = blockDisk.getBlockChunks(data, numBlocksNeeded);
+
+        byte[] resultData = new byte[0];
+
+        for (short i = 0; i < chunks.length; i++)
+        {
+            byte[] chunk = chunks[i];
+            byte[] newTotal = new byte[data.length + chunk.length];
+            // copy data into the new array
+            System.arraycopy(data, 0, newTotal, 0, data.length);
+            // copy the chunk into the new array
+            System.arraycopy(chunk, 0, newTotal, data.length, chunk.length);
+            // swap the new and old.
+            resultData = newTotal;
+        }
+
+        Serializable result = elementSerializer.deSerialize(resultData, null);
+        // System.out.println( result );
+        assertEquals("wrong string after retrieval", string, result);
+        blockDisk.close();
+    }
+
+    /**
+     * Verify that the block disk cache can handle a big string.
+     * <p>
+     *
+     * @throws Exception
+     */
+    public void testPutGet_BigString() throws Exception
+    {
+        String string = "This is my big string ABCDEFGH";
+        StringBuilder sb = new StringBuilder();
+        sb.append(string);
+        for (int i = 0; i < 4; i++)
+        {
+            sb.append(" " + i + sb.toString()); // big string
+        }
+        string = sb.toString();
+
+        String cacheName = "testPutGet_BigString";
+
+        BlockDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName(cacheName);
+        cattr.setMaxKeySize(100);
+        cattr.setBlockSizeBytes(200);
+        cattr.setDiskPath("target/test-sandbox/BlockDiskCacheUnitTest");
+        BlockDiskCache<String, String> diskCache = new BlockDiskCache<>(cattr);
+
+        // DO WORK
+        diskCache.update(new CacheElement<>(cacheName, "x", string));
+
+        // VERIFY
+        assertNotNull(diskCache.get("x"));
+        Thread.sleep(1000);
+        ICacheElement<String, String> afterElement = diskCache.get("x");
+        assertNotNull(afterElement);
+        // System.out.println( "afterElement = " + afterElement );
+        String after = afterElement.getVal();
+
+        assertNotNull(after);
+        assertEquals("wrong string after retrieval", string, after);
+    }
+
+    /**
+     * Verify that the block disk cache can handle utf encoded strings.
+     * <p>
+     *
+     * @throws Exception
+     */
+    public void testUTF8String() throws Exception
+    {
+        String string = "IÒtÎrn‚tiÙn‡lizÊti¯n";
+        StringBuilder sb = new StringBuilder();
+        sb.append(string);
+        for (int i = 0; i < 4; i++)
+        {
+            sb.append(sb.toString()); // big string
+        }
+        string = sb.toString();
+
+        // System.out.println( "The string contains " + string.length() + " characters" );
+
+        String cacheName = "testUTF8String";
+
+        BlockDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName(cacheName);
+        cattr.setMaxKeySize(100);
+        cattr.setBlockSizeBytes(200);
+        cattr.setDiskPath("target/test-sandbox/BlockDiskCacheUnitTest");
+        BlockDiskCache<String, String> diskCache = new BlockDiskCache<>(cattr);
+
+        // DO WORK
+        diskCache.update(new CacheElement<>(cacheName, "x", string));
+
+        // VERIFY
+        assertNotNull(diskCache.get("x"));
+        Thread.sleep(1000);
+        ICacheElement<String, String> afterElement = diskCache.get("x");
+        assertNotNull(afterElement);
+        // System.out.println( "afterElement = " + afterElement );
+        String after = afterElement.getVal();
+
+        assertNotNull(after);
+        assertEquals("wrong string after retrieval", string, after);
+    }
+
+    /**
+     * Verify that the block disk cache can handle utf encoded strings.
+     * <p>
+     *
+     * @throws Exception
+     */
+    public void testUTF8ByteArray() throws Exception
+    {
+        String string = "IÒtÎrn‚tiÙn‡lizÊti¯n";
+        StringBuilder sb = new StringBuilder();
+        sb.append(string);
+        for (int i = 0; i < 4; i++)
+        {
+            sb.append(sb.toString()); // big string
+        }
+        string = sb.toString();
+        // System.out.println( "The string contains " + string.length() + " characters" );
+        byte[] bytes = string.getBytes(StandardCharsets.UTF_8);
+
+        String cacheName = "testUTF8ByteArray";
+
+        BlockDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName(cacheName);
+        cattr.setMaxKeySize(100);
+        cattr.setBlockSizeBytes(200);
+        cattr.setDiskPath("target/test-sandbox/BlockDiskCacheUnitTest");
+        BlockDiskCache<String, byte[]> diskCache = new BlockDiskCache<>(cattr);
+
+        // DO WORK
+        diskCache.update(new CacheElement<>(cacheName, "x", bytes));
+
+        // VERIFY
+        assertNotNull(diskCache.get("x"));
+        Thread.sleep(1000);
+        ICacheElement<String, byte[]> afterElement = diskCache.get("x");
+        assertNotNull(afterElement);
+        // System.out.println( "afterElement = " + afterElement );
+        byte[] after = afterElement.getVal();
+
+        assertNotNull(after);
+        assertEquals("wrong bytes after retrieval", bytes.length, after.length);
+        // assertEquals( "wrong bytes after retrieval", bytes, after );
+        // assertEquals( "wrong bytes after retrieval", string, new String( after, StandardCharsets.UTF_8 ) );
+
+    }
+
+    /**
+     * Verify that the block disk cache can handle utf encoded strings.
+     * <p>
+     *
+     * @throws Exception
+     */
+    public void testUTF8StringAndBytes() throws Exception
+    {
+        X before = new X();
+        String string = "IÒtÎrn‚tiÙn‡lizÊti¯n";
+        StringBuilder sb = new StringBuilder();
+        sb.append(string);
+        for (int i = 0; i < 4; i++)
+        {
+            sb.append(sb.toString()); // big string
+        }
+        string = sb.toString();
+        // System.out.println( "The string contains " + string.length() + " characters" );
+        before.string = string;
+        before.bytes = string.getBytes(StandardCharsets.UTF_8);
+
+        String cacheName = "testUTF8StringAndBytes";
+
+        BlockDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName(cacheName);
+        cattr.setMaxKeySize(100);
+        cattr.setBlockSizeBytes(500);
+        cattr.setDiskPath("target/test-sandbox/BlockDiskCacheUnitTest");
+        BlockDiskCache<String, X> diskCache = new BlockDiskCache<>(cattr);
+
+        // DO WORK
+        diskCache.update(new CacheElement<>(cacheName, "x", before));
+
+        // VERIFY
+        assertNotNull(diskCache.get("x"));
+        Thread.sleep(1000);
+        ICacheElement<String, X> afterElement = diskCache.get("x");
+        // System.out.println( "afterElement = " + afterElement );
+        X after = (afterElement.getVal());
+
+        assertNotNull(after);
+        assertEquals("wrong string after retrieval", string, after.string);
+        assertEquals("wrong bytes after retrieval", string, new String(after.bytes, StandardCharsets.UTF_8));
+
+    }
+
+    public void testLoadFromDisk() throws Exception
+    {
+        for (int i = 0; i < 20; i++)
+        { // usually after 2 time it fails
+            oneLoadFromDisk();
+        }
+    }
+
+    public void testAppendToDisk() throws Exception
+    {
+        String cacheName = "testAppendToDisk";
+        BlockDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName(cacheName);
+        cattr.setMaxKeySize(100);
+        cattr.setBlockSizeBytes(500);
+        cattr.setDiskPath("target/test-sandbox/BlockDiskCacheUnitTest");
+        BlockDiskCache<String, X> diskCache = new BlockDiskCache<>(cattr);
+        diskCache.removeAll();
+        X value1 = new X();
+        value1.string = "1234567890";
+        X value2 = new X();
+        value2.string = "0987654321";
+        diskCache.update(new CacheElement<>(cacheName, "1", value1));
+        diskCache.dispose();
+        diskCache = new BlockDiskCache<>(cattr);
+        diskCache.update(new CacheElement<>(cacheName, "2", value2));
+        diskCache.dispose();
+        diskCache = new BlockDiskCache<>(cattr);
+        assertTrue(diskCache.verifyDisk());
+        assertEquals(2, diskCache.getKeySet().size());
+        assertEquals(value1.string, diskCache.get("1").getVal().string);
+        assertEquals(value2.string, diskCache.get("2").getVal().string);
+    }
+
+    public void oneLoadFromDisk() throws Exception
+    {
+        // initialize object to be stored
+        X before = new X();
+        String string = "IÒtÎrn‚tiÙn‡lizÊti¯n";
+        StringBuilder sb = new StringBuilder();
+        sb.append(string);
+        for (int i = 0; i < 4; i++)
+        {
+            sb.append(sb.toString()); // big string
+        }
+        string = sb.toString();
+        before.string = string;
+        before.bytes = string.getBytes(StandardCharsets.UTF_8);
+
+        // initialize cache
+        String cacheName = "testLoadFromDisk";
+        BlockDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName(cacheName);
+        cattr.setMaxKeySize(100);
+        cattr.setBlockSizeBytes(500);
+        cattr.setDiskPath("target/test-sandbox/BlockDiskCacheUnitTest");
+        BlockDiskCache<String, X> diskCache = new BlockDiskCache<>(cattr);
+
+        // DO WORK
+        for (int i = 0; i < 50; i++)
+        {
+            diskCache.update(new CacheElement<>(cacheName, "x" + i, before));
+        }
+        diskCache.dispose();
+
+        // VERIFY
+        diskCache = new BlockDiskCache<>(cattr);
+
+        for (int i = 0; i < 50; i++)
+        {
+            ICacheElement<String, X> afterElement = diskCache.get("x" + i);
+            assertNotNull("Missing element from cache. Cache size: " + diskCache.getSize() + " element: x" + i, afterElement);
+            X after = (afterElement.getVal());
+
+            assertNotNull(after);
+            assertEquals("wrong string after retrieval", string, after.string);
+            assertEquals("wrong bytes after retrieval", string, new String(after.bytes, StandardCharsets.UTF_8));
+        }
+
+        diskCache.dispose();
+    }
+
+    /**
+     * Add some items to the disk cache and then remove them one by one.
+     *
+     * @throws IOException
+     */
+    public void testRemoveItems() throws IOException
+    {
+        BlockDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName("testRemoveItems");
+        cattr.setMaxKeySize(100);
+        cattr.setDiskPath("target/test-sandbox/BlockDiskCacheUnitTest");
+        BlockDiskCache<String, String> disk = new BlockDiskCache<>(cattr);
+
+        disk.processRemoveAll();
+
+        int cnt = 25;
+        for (int i = 0; i < cnt; i++)
+        {
+            IElementAttributes eAttr = new ElementAttributes();
+            eAttr.setIsSpool(true);
+            ICacheElement<String, String> element = new CacheElement<>("testRemoveItems", "key:" + i, "data:" + i);
+            element.setElementAttributes(eAttr);
+            disk.processUpdate(element);
+        }
+
+        // remove each
+        for (int i = 0; i < cnt; i++)
+        {
+            disk.remove("key:" + i);
+            ICacheElement<String, String> element = disk.processGet("key:" + i);
+            assertNull("Should not have received an element.", element);
+        }
+    }
+
+    /**
+     * Add some items to the disk cache and then remove them one by one.
+     * <p>
+     *
+     * @throws IOException
+     */
+    public void testRemove_PartialKey() throws IOException
+    {
+        BlockDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName("testRemove_PartialKey");
+        cattr.setMaxKeySize(100);
+        cattr.setDiskPath("target/test-sandbox/BlockDiskCacheUnitTest");
+        BlockDiskCache<String, String> disk = new BlockDiskCache<>(cattr);
+
+        disk.processRemoveAll();
+
+        int cnt = 25;
+        for (int i = 0; i < cnt; i++)
+        {
+            IElementAttributes eAttr = new ElementAttributes();
+            eAttr.setIsSpool(true);
+            ICacheElement<String, String> element = new CacheElement<>("testRemove_PartialKey", i + ":key", "data:"
+                + i);
+            element.setElementAttributes(eAttr);
+            disk.processUpdate(element);
+        }
+
+        // verify each
+        for (int i = 0; i < cnt; i++)
+        {
+            ICacheElement<String, String> element = disk.processGet(i + ":key");
+            assertNotNull("Shoulds have received an element.", element);
+        }
+
+        // remove each
+        for (int i = 0; i < cnt; i++)
+        {
+            disk.remove(i + ":");
+            ICacheElement<String, String> element = disk.processGet(i + ":key");
+            assertNull("Should not have received an element.", element);
+        }
+    }
+
+
+    /**
+     * Verify that group members are removed if we call remove with a group.
+     *
+     * @throws IOException
+     */
+    public void testRemove_Group() throws IOException
+    {
+        // SETUP
+        BlockDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName("testRemove_Group");
+        cattr.setMaxKeySize(100);
+        cattr.setDiskPath("target/test-sandbox/BlockDiskCacheUnitTest");
+        BlockDiskCache<GroupAttrName<String>, String> disk = new BlockDiskCache<>(cattr);
+
+        disk.processRemoveAll();
+
+        String cacheName = "testRemove_Group_Region";
+        String groupName = "testRemove_Group";
+
+        int cnt = 25;
+        for (int i = 0; i < cnt; i++)
+        {
+            GroupAttrName<String> groupAttrName = getGroupAttrName(cacheName, groupName, i + ":key");
+            CacheElement<GroupAttrName<String>, String> element = new CacheElement<>(cacheName,
+                groupAttrName, "data:" + i);
+
+            IElementAttributes eAttr = new ElementAttributes();
+            eAttr.setIsSpool(true);
+            element.setElementAttributes(eAttr);
+
+            disk.processUpdate(element);
+        }
+
+        // verify each
+        for (int i = 0; i < cnt; i++)
+        {
+            GroupAttrName<String> groupAttrName = getGroupAttrName(cacheName, groupName, i + ":key");
+            ICacheElement<GroupAttrName<String>, String> element = disk.processGet(groupAttrName);
+            assertNotNull("Should have received an element.", element);
+        }
+
+        // DO WORK
+        // remove the group
+        disk.remove(getGroupAttrName(cacheName, groupName, null));
+
+        for (int i = 0; i < cnt; i++)
+        {
+            GroupAttrName<String> groupAttrName = getGroupAttrName(cacheName, groupName, i + ":key");
+            ICacheElement<GroupAttrName<String>, String> element = disk.processGet(groupAttrName);
+
+            // VERIFY
+            assertNull("Should not have received an element.", element);
+        }
+
+    }
+
+    /**
+     * Internal method used for group functionality.
+     * <p>
+     *
+     * @param cacheName
+     * @param group
+     * @param name
+     * @return GroupAttrName
+     */
+    private GroupAttrName<String> getGroupAttrName(String cacheName, String group, String name)
+    {
+        GroupId gid = new GroupId(cacheName, group);
+        return new GroupAttrName<>(gid, name);
+    }
+
+    /** Holder for a string and byte array. */
+    static class X implements Serializable
+    {
+        /** ignore */
+        private static final long serialVersionUID = 1L;
+
+        /** Test string */
+        String string;
+
+        /*** test byte array. */
+        byte[] bytes;
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskUnitTest.java
new file mode 100644
index 0000000..a09bdb0
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/block/BlockDiskUnitTest.java
@@ -0,0 +1,369 @@
+package org.apache.commons.jcs3.auxiliary.disk.block;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Random;
+
+import org.apache.commons.jcs3.auxiliary.disk.block.BlockDisk;
+import org.apache.commons.jcs3.utils.serialization.StandardSerializer;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/**
+ * Test for the disk access layer of the Block Disk Cache.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class BlockDiskUnitTest
+    extends TestCase
+{
+    /** data file. */
+    private File rafDir;
+    private BlockDisk disk;
+
+    /**
+     * @see junit.framework.TestCase#setUp()
+     * Creates the base directory
+     */
+    @Override
+    protected void setUp() throws Exception
+    {
+        super.setUp();
+        String rootDirName = "target/test-sandbox/block";
+        this.rafDir = new File( rootDirName );
+        this.rafDir.mkdirs();
+    }
+
+    private void setUpBlockDisk(String fileName) throws IOException
+    {
+        File file = new File(rafDir, fileName + ".data");
+        file.delete();
+        this.disk = new BlockDisk(file, new StandardSerializer());
+    }
+
+    private void setUpBlockDisk(String fileName, int blockSize) throws IOException
+    {
+        File file = new File(rafDir, fileName + ".data");
+        file.delete();
+        this.disk = new BlockDisk(file, blockSize, new StandardSerializer());
+    }
+
+    /**
+     * @see junit.framework.TestCase#tearDown()
+     */
+    @Override
+    protected void tearDown() throws Exception
+    {
+        disk.close();
+        super.tearDown();
+    }
+
+    /**
+     * Test writing a null object within a single block size.
+     * <p>
+     * @throws Exception
+     */
+    public void testWrite_NullBlockElement()
+        throws Exception
+    {
+        // SETUP
+        setUpBlockDisk("testWrite_NullBlockElement");
+
+        // DO WORK
+        int[] blocks = disk.write( null );
+
+        // VERIFY
+        assertEquals( "Wrong number of blocks recorded.", 1, disk.getNumberOfBlocks() );
+        assertEquals( "Wrong number of blocks returned.", 1, blocks.length );
+        assertEquals( "Wrong block returned.", 0, blocks[0] );
+    }
+
+    /**
+     * Test writing an element within a single block size.
+     * <p>
+     * @throws Exception
+     */
+    public void testWrite_SingleBlockElement()
+        throws Exception
+    {
+        // SETUP
+        setUpBlockDisk("testWrite_SingleBlockElement");
+
+        // DO WORK
+        int bytes = 1 * 1024;
+        int[] blocks = disk.write( new byte[bytes] );
+
+        // VERIFY
+        assertEquals( "Wrong number of blocks recorded.", 1, disk.getNumberOfBlocks() );
+        assertEquals( "Wrong number of blocks returned.", 1, blocks.length );
+        assertEquals( "Wrong block returned.", 0, blocks[0] );
+    }
+
+    /**
+     * Test writing and reading an element within a single block size.
+     * <p>
+     * @throws Exception
+     */
+    public void testWriteAndRead_SingleBlockElement()
+        throws Exception
+    {
+        // SETUP
+        setUpBlockDisk("testWriteAndRead_SingleBlockElement");
+
+        // DO WORK
+        int bytes = 1 * 1024;
+        int[] blocks = disk.write( new byte[bytes] );
+
+        byte[] result = (byte[]) disk.read( blocks );
+
+        // VERIFY
+        assertEquals( "Wrong item retured.", new byte[bytes].length, result.length );
+    }
+
+    /**
+     * Test writing two elements that each fit within a single block size.
+     * <p>
+     * @throws Exception
+     */
+    public void testWrite_TwoSingleBlockElements()
+        throws Exception
+    {
+        // SETUP
+        setUpBlockDisk("testWrite_TwoSingleBlockElements");
+
+        // DO WORK
+        int bytes = 1 * 1024;
+        int[] blocks1 = disk.write( new byte[bytes] );
+        int[] blocks2 = disk.write( new byte[bytes] );
+
+        // VERIFY
+        assertEquals( "Wrong number of blocks recorded.", 2, disk.getNumberOfBlocks() );
+        assertEquals( "Wrong number of blocks returned.", 1, blocks1.length );
+        assertEquals( "Wrong block returned.", 0, blocks1[0] );
+        assertEquals( "Wrong number of blocks returned.", 1, blocks2.length );
+        assertEquals( "Wrong block returned.", 1, blocks2[0] );
+    }
+
+    /**
+     * Verify that it says we need two blocks if the total size will fit.
+     * <p>
+     * @throws Exception
+     */
+    public void testCalculateBlocksNeededDouble()
+        throws Exception
+    {
+        // SETUP
+        setUpBlockDisk("testCalculateBlocksNeededDouble");
+
+        // DO WORK
+        int result = disk.calculateTheNumberOfBlocksNeeded( new byte[disk.getBlockSizeBytes() * 2
+            - ( 2 * BlockDisk.HEADER_SIZE_BYTES )] );
+
+        // Verify
+        assertEquals( "Wrong number of blocks", 2, result );
+    }
+
+    /**
+     * Test writing an element that takes two blocks.
+     * <p>
+     * @throws Exception
+     */
+    public void testWrite_DoubleBlockElement()
+        throws Exception
+    {
+        // SETUP
+        setUpBlockDisk("testWriteDoubleBlockElement");
+
+        // DO WORK
+        // byte arrays encur 27 bytes of serialization overhead.
+        int bytes = getBytesForBlocksOfByteArrays( disk.getBlockSizeBytes(), 2 );
+        int[] blocks = disk.write( new byte[bytes] );
+
+        // VERIFY
+        assertEquals( "Wrong number of blocks recorded.", 2, disk.getNumberOfBlocks() );
+        assertEquals( "Wrong number of blocks returned.", 2, blocks.length );
+        assertEquals( "Wrong block returned.", 0, blocks[0] );
+    }
+
+    /**
+     * Test writing an element that takes 128 blocks.  There was a byte in a for loop that limited the number to 127.  I fixed this.
+     * <p>
+     * @throws Exception
+     */
+    public void testWrite_128BlockElement()
+        throws Exception
+    {
+        // SETUP
+        int numBlocks = 128;
+
+        setUpBlockDisk("testWrite_128BlockElement");
+
+        // DO WORK
+        // byte arrays encur 27 bytes of serialization overhead.
+        int bytes = getBytesForBlocksOfByteArrays( disk.getBlockSizeBytes(), numBlocks );
+        int[] blocks = disk.write( new byte[bytes] );
+
+        // VERIFY
+        assertEquals( "Wrong number of blocks recorded.", numBlocks, disk.getNumberOfBlocks() );
+        assertEquals( "Wrong number of blocks returned.", numBlocks, blocks.length );
+        assertEquals( "Wrong block returned.", 0, blocks[0] );
+    }
+
+    /**
+     * Test writing and reading elements that do not fit within a single block.
+     * <p>
+     * @throws Exception
+     */
+    public void testWriteAndReadMultipleMultiBlockElement()
+        throws Exception
+    {
+        // SETUP
+        setUpBlockDisk("testWriteAndReadSingleBlockElement");
+
+        // DO WORK
+        int numBlocksPerElement = 4;
+        int bytes = getBytesForBlocksOfByteArrays( disk.getBlockSizeBytes(), numBlocksPerElement );
+
+        int numElements = 100;
+        for ( int i = 0; i < numElements; i++ )
+        {
+            int[] blocks = disk.write( new byte[bytes] );
+            byte[] result = (byte[]) disk.read( blocks );
+
+            // VERIFY
+            assertEquals( "Wrong item retured.", new byte[bytes].length, result.length );
+            assertEquals( "Wrong number of blocks returned.", numBlocksPerElement, blocks.length );
+        }
+    }
+
+    /**
+     * Test writing and reading elements that do not fit within a single block.
+     * <p>
+     * @throws Exception
+     */
+    public void testWriteAndReadMultipleMultiBlockElement_setSize()
+        throws Exception
+    {
+        // SETUP
+        setUpBlockDisk("testWriteAndReadSingleBlockElement", 1024);
+
+        // DO WORK
+        int numBlocksPerElement = 4;
+        int bytes = getBytesForBlocksOfByteArrays( disk.getBlockSizeBytes(), numBlocksPerElement );
+
+        int numElements = 100;
+        Random r = new Random(System.currentTimeMillis());
+        final byte[] src = new byte[bytes];
+        for ( int i = 0; i < numElements; i++ )
+        {
+            r.nextBytes(src);  // Ensure we don't just write zeros out
+            int[] blocks = disk.write( src );
+            byte[] result = (byte[]) disk.read( blocks );
+
+            // VERIFY
+            assertEquals( "Wrong item length retured.", src.length, result.length );
+            assertEquals( "Wrong number of blocks returned.", numBlocksPerElement, blocks.length );
+
+            // We check the array contents, too, to ensure we read back what we wrote out
+            for (int j = 0 ; j < src.length ; j++) {
+                assertEquals( "Mismatch at offset " + j + " in attempt # " + (i + 1), src[j], result[j] );
+            }
+        }
+        assertEquals( "Wrong number of elements. "+disk, numBlocksPerElement * numElements, disk.getNumberOfBlocks() );
+    }
+
+    /**
+     * Used to get the size for byte arrays that will take up the number of blocks specified.
+     * <p>
+     * @param blockSize
+     * @param numBlocks
+     * @return num bytes.
+     */
+    private int getBytesForBlocksOfByteArrays( int blockSize, int numBlocks )
+    {
+        // byte arrays encur some bytes of serialization overhead.
+        return blockSize * numBlocks - ( numBlocks * BlockDisk.HEADER_SIZE_BYTES ) - ( numBlocks * 14 );
+    }
+
+    /**
+     * Verify that the block disk can handle a big string.
+     * <p>
+     * @throws Exception
+     */
+    public void testWriteAndRead_BigString()
+        throws Exception
+    {
+        // SETUP
+        setUpBlockDisk("testWriteAndRead_BigString", 4096); //1024
+
+        String string = "This is my big string ABCDEFGH";
+        StringBuilder sb = new StringBuilder();
+        sb.append( string );
+        for ( int i = 0; i < 8; i++ )
+        {
+            sb.append( " " + i + sb.toString() ); // big string
+        }
+        string = sb.toString();
+
+        // DO WORK
+        int[] blocks = disk.write( string );
+        String result = (String) disk.read( blocks );
+
+        // VERIFY
+//        System.out.println( string );
+//        System.out.println( result );
+//        System.out.println( disk );
+        assertEquals( "Wrong item retured.", string, result );
+    }
+
+    /**
+     * Verify that the block disk can handle a big string.
+     * <p>
+     * @throws Exception
+     */
+    public void testWriteAndRead_BigString2()
+        throws Exception
+    {
+        // SETUP
+        setUpBlockDisk("testWriteAndRead_BigString", 47); //4096;//1024
+
+        String string = "abcdefghijklmnopqrstuvwxyz1234567890";
+        string += string;
+        string += string;
+
+        // DO WORK
+        int[] blocks = disk.write( string );
+        String result = (String) disk.read( blocks );
+
+        // VERIFY
+        assertEquals( "Wrong item retured.", string, result );
+    }
+
+    public void testJCS156() throws Exception
+    {
+        // SETUP
+        setUpBlockDisk("testJCS156", 4096);
+        long offset = disk.calculateByteOffsetForBlockAsLong(Integer.MAX_VALUE);
+        assertTrue("Must not wrap round", offset > 0);
+        assertEquals(Integer.MAX_VALUE*4096L,offset);
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/block/HugeQuantityBlockDiskCacheLoadTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/block/HugeQuantityBlockDiskCacheLoadTest.java
new file mode 100644
index 0000000..aee9b5e
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/block/HugeQuantityBlockDiskCacheLoadTest.java
@@ -0,0 +1,136 @@
+package org.apache.commons.jcs3.auxiliary.disk.block;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+import org.apache.commons.jcs3.utils.timing.SleepUtil;
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.utils.timing.ElapsedTimer;
+
+/**
+ * Put a few hundred thousand entries in the block disk cache.
+ * @author Aaron Smuts
+ */
+public class HugeQuantityBlockDiskCacheLoadTest
+    extends TestCase
+{
+
+    /**
+     * Test setup
+     */
+    @Override
+    public void setUp()
+    {
+        JCS.setConfigFilename( "/TestBlockDiskCacheHuge.ccf" );
+    }
+
+    /**
+     * Adds items to cache, gets them, and removes them. The item count is more than the size of the
+     * memory cache, so items should spool to disk.
+     * <p>
+     * @throws Exception If an error occurs
+     */
+    public void testLargeNumberOfItems()
+        throws Exception
+    {
+        int items = 300000;
+        String region = "testCache1";
+
+        System.out.println( "--------------------------" );
+        long initialMemory = measureMemoryUse();
+        System.out.println( "Before getting JCS: " + initialMemory );
+
+        CacheAccess<String, String> jcs = JCS.getInstance( region );
+        jcs.clear();
+
+        try
+        {
+            ElapsedTimer timer = new ElapsedTimer();
+            System.out.println( "Start: " + measureMemoryUse() );
+
+            // Add items to cache
+            for ( int i = 0; i <= items; i++ )
+            {
+                jcs.put( i + ":key", region + " data " + i );
+            }
+
+            System.out.println( jcs.getStats() );
+            System.out.println( "--------------------------" );
+            System.out.println( "After put: " + measureMemoryUse() );
+
+            Thread.sleep( 5000 );
+
+            System.out.println( jcs.getStats() );
+            System.out.println( "--------------------------" );
+            System.out.println( "After wait: " + measureMemoryUse() );
+
+            for ( int i = 0; i < 10; i++ )
+            {
+                SleepUtil.sleepAtLeast( 3000 );
+                System.out.println( "--------------------------" );
+                System.out.println( "After sleep. " + timer.getElapsedTimeString() + " memory used = "
+                    + measureMemoryUse() );
+                System.out.println( jcs.getStats() );
+            }
+
+            // Test that all items are in cache
+            System.out.println( "--------------------------" );
+            System.out.println( "Retrieving all." );
+            for ( int i = 0; i <= items; i++ )
+            {
+                //System.out.print(  "\033[s" );
+                String value = jcs.get( i + ":key" );
+                if ( i % 1000 == 0 )
+                {
+                    //System.out.print(  "\033[r" );
+                    System.out.println( i + " " );
+                }
+                assertEquals( "Wrong value returned.", region + " data " + i, value );
+            }
+            long aftetGet = measureMemoryUse();
+            System.out.println( "After get: " + aftetGet + " diff = " + ( aftetGet - initialMemory ) );
+
+        }
+        finally
+        {
+            // dump the stats to the report
+            System.out.println( jcs.getStats() );
+            System.out.println( "--------------------------" );
+            long endMemory = measureMemoryUse();
+            System.out.println( "End: " + endMemory + " diff = " + ( endMemory - initialMemory ) );
+        }
+    }
+
+    /**
+     * Measure memory used by the VM.
+     * @return long
+     * @throws InterruptedException
+     */
+    protected long measureMemoryUse()
+        throws InterruptedException
+    {
+        System.gc();
+        Thread.sleep( 3000 );
+        System.gc();
+        return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/DiskTestObjectUtil.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/DiskTestObjectUtil.java
new file mode 100644
index 0000000..779ba3c
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/DiskTestObjectUtil.java
@@ -0,0 +1,144 @@
+package org.apache.commons.jcs3.auxiliary.disk.indexed;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.Random;
+
+import org.apache.commons.jcs3.auxiliary.disk.DiskTestObject;
+import org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDisk;
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.utils.serialization.StandardSerializer;
+
+/**
+ * Utility for dealing with test objects.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class DiskTestObjectUtil
+{
+    /**
+     * Total from the start to the endPostion.
+     * <p>
+     * @param testObjects
+     * @param endPosition
+     * @return size
+     * @throws IOException
+     */
+    public static long totalSize( DiskTestObject[] testObjects, int endPosition )
+        throws IOException
+    {
+        StandardSerializer serializer = new StandardSerializer();
+        long total = 0;
+        for ( int i = 0; i < endPosition; i++ )
+        {
+            int tileSize = serializer.serialize( testObjects[i] ).length + IndexedDisk.HEADER_SIZE_BYTES;
+            total += tileSize;
+        }
+        return total;
+    }
+
+    /**
+     * Total from the start to the endPostion.
+     * <p>
+     * @param elements
+     * @param endPosition
+     * @return size
+     * @throws IOException
+     */
+    public static <K, V> long totalSize( ICacheElement<K, V>[] elements, int endPosition )
+        throws IOException
+    {
+        return totalSize( elements, 0, endPosition );
+    }
+
+    /**
+     * Total from the start to the endPostion.
+     * <p>
+     * @param elements
+     * @param startPosition
+     * @param endPosition
+     * @return size
+     * @throws IOException
+     */
+    public static <K, V> long totalSize( ICacheElement<K, V>[] elements, int startPosition, int endPosition )
+        throws IOException
+    {
+        StandardSerializer serializer = new StandardSerializer();
+        long total = 0;
+        for ( int i = startPosition; i < endPosition; i++ )
+        {
+            int tileSize = serializer.serialize( elements[i] ).length + IndexedDisk.HEADER_SIZE_BYTES;
+            total += tileSize;
+        }
+        return total;
+    }
+
+    /**
+     * Creates an array of ICacheElements with DiskTestObjects with payloads the byte size.
+     * <p>
+     * @param numToCreate
+     * @param bytes
+     * @param cacheName
+     * @return ICacheElement[]
+     */
+    public static ICacheElement<Integer, DiskTestObject>[] createCacheElementsWithTestObjects( int numToCreate, int bytes, String cacheName )
+    {
+        @SuppressWarnings("unchecked")
+        ICacheElement<Integer, DiskTestObject>[] elements = new ICacheElement[numToCreate];
+        for ( int i = 0; i < numToCreate; i++ )
+        {
+            // 24 KB
+            int size = bytes * 1024;
+            DiskTestObject tile = new DiskTestObject( Integer.valueOf( i ), new byte[size] );
+
+            ICacheElement<Integer, DiskTestObject> element = new CacheElement<>( cacheName, tile.id, tile );
+            elements[i] = element;
+        }
+        return elements;
+    }
+
+    /**
+     * Creates an array of ICacheElements with DiskTestObjects with payloads the byte size.
+     * <p>
+     * @param numToCreate
+     * @param cacheName
+     * @return ICacheElement[]
+     */
+    public static ICacheElement<Integer, DiskTestObject>[] createCacheElementsWithTestObjectsOfVariableSizes( int numToCreate, String cacheName )
+    {
+        @SuppressWarnings("unchecked")
+        ICacheElement<Integer, DiskTestObject>[] elements = new ICacheElement[numToCreate];
+        Random random = new Random( 89 );
+        for ( int i = 0; i < numToCreate; i++ )
+        {
+            int bytes = random.nextInt( 20 );
+            // 4-24 KB
+            int size = ( bytes + 4 ) * 1024;
+            DiskTestObject tile = new DiskTestObject( Integer.valueOf( i ), new byte[size] );
+
+            ICacheElement<Integer, DiskTestObject> element = new CacheElement<>( cacheName, tile.id, tile );
+            elements[i] = element;
+        }
+        return elements;
+    }
+
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/HugeQuantityIndDiskCacheLoadTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/HugeQuantityIndDiskCacheLoadTest.java
new file mode 100644
index 0000000..4ea19a6
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/HugeQuantityIndDiskCacheLoadTest.java
@@ -0,0 +1,125 @@
+package org.apache.commons.jcs3.auxiliary.disk.indexed;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/**
+ * Put a few hundred thousand entries in the disk cache.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class HugeQuantityIndDiskCacheLoadTest
+    extends TestCase
+{
+    /** Test setup.  */
+    @Override
+    public void setUp()
+    {
+        JCS.setConfigFilename( "/TestDiskCacheHuge.ccf" );
+    }
+
+    /**
+     * Adds items to cache, gets them, and removes them. The item count is more than the size of the
+     * memory cache, so items should spool to disk.
+     * <p>
+     * @throws Exception If an error occurs
+     */
+    public void testLargeNumberOfItems()
+        throws Exception
+    {
+        int items = 300000;
+        String region = "testCache1";
+
+        CacheAccess<String, String> jcs = JCS.getInstance( region );
+
+        try
+        {
+            System.out.println( "Start: " + measureMemoryUse() );
+
+            // Add items to cache
+
+            for ( int i = 0; i <= items; i++ )
+            {
+                jcs.put( i + ":key", region + " data " + i );
+            }
+
+            System.out.println( jcs.getStats() );
+            System.out.println( "--------------------------" );
+            System.out.println( "After put: " + measureMemoryUse() );
+
+            Thread.sleep( 5000 );
+
+            System.out.println( jcs.getStats() );
+            System.out.println( "--------------------------" );
+            System.out.println( "After wait: " + measureMemoryUse() );
+
+            // Test that all items are in cache
+
+            for ( int i = 0; i <= items; i++ )
+            {
+                String value = jcs.get( i + ":key" );
+
+                assertEquals( region + " data " + i, value );
+            }
+
+            System.out.println( "After get: " + measureMemoryUse() );
+
+            // // Remove all the items
+            // for ( int i = 0; i <= items; i++ )
+            // {
+            // jcs.remove( i + ":key" );
+            // }
+            //
+            // // Verify removal
+            // for ( int i = 0; i <= items; i++ )
+            // {
+            // assertNull( "Removed key should be null: " + i + ":key" + "\n
+            // stats " + jcs.getStats(), jcs.get( i + ":key" ) );
+            // }
+
+        }
+        finally
+        {
+            // dump the stats to the report
+            System.out.println( jcs.getStats() );
+            System.out.println( "--------------------------" );
+            System.out.println( "End: " + measureMemoryUse() );
+        }
+    }
+
+    /**
+     * Measure memory used by the VM.
+     * <p>
+     * @return memory used
+     * @throws InterruptedException
+     */
+    protected long measureMemoryUse()
+        throws InterruptedException
+    {
+        System.gc();
+        Thread.sleep( 3000 );
+        System.gc();
+        return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexDiskCacheCountUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexDiskCacheCountUnitTest.java
new file mode 100644
index 0000000..aec3d75
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexDiskCacheCountUnitTest.java
@@ -0,0 +1,111 @@
+package org.apache.commons.jcs3.auxiliary.disk.indexed;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+
+import org.apache.commons.jcs3.auxiliary.disk.behavior.IDiskCacheAttributes.DiskLimitType;
+import org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCache;
+import org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes;
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+
+public class IndexDiskCacheCountUnitTest extends IndexDiskCacheUnitTestAbstract {
+
+	@Override
+	public IndexedDiskCacheAttributes getCacheAttributes() {
+		IndexedDiskCacheAttributes ret = new IndexedDiskCacheAttributes();
+		ret.setDiskLimitType(DiskLimitType.COUNT);
+		return ret;
+	}
+	  public void testRecycleBin()
+		        throws IOException
+		    {
+		        IndexedDiskCacheAttributes cattr = getCacheAttributes();
+		        cattr.setCacheName( "testRemoveItems" );
+		        cattr.setOptimizeAtRemoveCount( 7 );
+		        cattr.setMaxKeySize( 5 );
+		        cattr.setMaxPurgatorySize( 0 );
+		        cattr.setDiskPath( "target/test-sandbox/BreakIndexTest" );
+		        IndexedDiskCache<String, String> disk = new IndexedDiskCache<>( cattr );
+
+		        String[] test = { "a", "bb", "ccc", "dddd", "eeeee", "ffffff", "ggggggg", "hhhhhhhhh", "iiiiiiiiii" };
+		        String[] expect = { null, "bb", "ccc", null, null, "ffffff", null, "hhhhhhhhh", "iiiiiiiiii" };
+
+		        //System.out.println( "------------------------- testRecycleBin " );
+
+		        for ( int i = 0; i < 6; i++ )
+		        {
+		            ICacheElement<String, String> element = new CacheElement<>( "testRecycleBin", "key:" + test[i], test[i] );
+		            //System.out.println( "About to add " + "key:" + test[i] + " i = " + i );
+		            disk.processUpdate( element );
+		        }
+
+		        for ( int i = 3; i < 5; i++ )
+		        {
+		            //System.out.println( "About to remove " + "key:" + test[i] + " i = " + i );
+		            disk.remove( "key:" + test[i] );
+		        }
+
+		        // there was a bug where 7 would try to be put in the empty slot left by 4's removal, but it
+		        // will not fit.
+		        for ( int i = 7; i < 9; i++ )
+		        {
+		            ICacheElement<String, String> element = new CacheElement<>( "testRecycleBin", "key:" + test[i], test[i] );
+		            //System.out.println( "About to add " + "key:" + test[i] + " i = " + i );
+		            disk.processUpdate( element );
+		        }
+
+		        try
+		        {
+		            for ( int i = 0; i < 9; i++ )
+		            {
+		                ICacheElement<String, String> element = disk.get( "key:" + test[i] );
+		                if ( element != null )
+		                {
+		                    //System.out.println( "element = " + element.getVal() );
+		                }
+		                else
+		                {
+		                    //System.out.println( "null --" + "key:" + test[i] );
+		                }
+
+		                String expectedValue = expect[i];
+		                if ( expectedValue == null )
+		                {
+		                    assertNull( "Expected a null element", element );
+		                }
+		                else
+		                {
+		                    assertNotNull( "The element for key [" + "key:" + test[i] + "] should not be null. i = " + i,
+		                                   element );
+		                    assertEquals( "Elements contents do not match expected", element.getVal(), expectedValue );
+		                }
+		            }
+		        }
+		        catch ( Exception e )
+		        {
+		            e.printStackTrace();
+		            fail( "Should not get an exception: " + e.toString() );
+		        }
+
+		        disk.removeAll();
+		    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexDiskCacheSizeUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexDiskCacheSizeUnitTest.java
new file mode 100644
index 0000000..4fafde9
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexDiskCacheSizeUnitTest.java
@@ -0,0 +1,112 @@
+package org.apache.commons.jcs3.auxiliary.disk.indexed;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+
+import org.apache.commons.jcs3.auxiliary.disk.DiskTestObject;
+import org.apache.commons.jcs3.auxiliary.disk.behavior.IDiskCacheAttributes.DiskLimitType;
+import org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCache;
+import org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes;
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+
+public class IndexDiskCacheSizeUnitTest extends IndexDiskCacheUnitTestAbstract {
+
+	@Override
+	public IndexedDiskCacheAttributes getCacheAttributes() {
+		IndexedDiskCacheAttributes ret = new IndexedDiskCacheAttributes();
+		ret.setDiskLimitType(DiskLimitType.SIZE);
+		return ret;
+	}
+	  public void testRecycleBin()
+		        throws IOException
+		    {
+		        IndexedDiskCacheAttributes cattr = getCacheAttributes();
+		        cattr.setCacheName( "testRemoveItems" );
+		        cattr.setOptimizeAtRemoveCount( 7 );
+		        cattr.setMaxKeySize( 8); // 1kb DiskTestObject takes 1420 bytes, so 5*1420 = 7100, so to keep 5 ojbects, we need max key size of 8
+		        cattr.setMaxPurgatorySize( 0 );
+		        cattr.setDiskPath( "target/test-sandbox/BreakIndexTest" );
+		        IndexedDiskCache<String, DiskTestObject> disk = new IndexedDiskCache<>( cattr );
+
+		        String[] test = { "a", "bb", "ccc", "dddd", "eeeee", "ffffff", "ggggggg", "hhhhhhhhh", "iiiiiiiiii" };
+		        String[] expect = { null, "bb", "ccc", null, null, "ffffff", null, "hhhhhhhhh", "iiiiiiiiii" };
+		        DiskTestObject value = DiskTestObjectUtil.createCacheElementsWithTestObjects( 1, 1, cattr .getCacheName())[0].getVal();
+		        //System.out.println( "------------------------- testRecycleBin " );
+
+		        for ( int i = 0; i < 6; i++ )
+		        {
+		            ICacheElement<String, DiskTestObject> element = new CacheElement<>( "testRecycleBin", "key:" + test[i], value);
+		            //System.out.println( "About to add " + "key:" + test[i] + " i = " + i );
+		            disk.processUpdate( element );
+		        }
+
+		        for ( int i = 3; i < 5; i++ )
+		        {
+		            //System.out.println( "About to remove " + "key:" + test[i] + " i = " + i );
+		            disk.remove( "key:" + test[i] );
+		        }
+
+		        // there was a bug where 7 would try to be put in the empty slot left by 4's removal, but it
+		        // will not fit.
+		        for ( int i = 7; i < 9; i++ )
+		        {
+		            ICacheElement<String, DiskTestObject> element = new CacheElement<>( "testRecycleBin", "key:" + test[i], value);
+		            //System.out.println( "About to add " + "key:" + test[i] + " i = " + i );
+		            disk.processUpdate( element );
+		        }
+
+		        try
+		        {
+		            for ( int i = 0; i < 9; i++ )
+		            {
+		                ICacheElement<String, DiskTestObject> element = disk.get( "key:" + test[i] );
+		                if ( element != null )
+		                {
+		                    //System.out.println( "element = " + element.getVal() );
+		                }
+		                else
+		                {
+		                    //System.out.println( "null --" + "key:" + test[i] );
+		                }
+
+		                String expectedValue = expect[i];
+		                if ( expectedValue == null )
+		                {
+		                    assertNull( "Expected a null element", element );
+		                }
+		                else
+		                {
+		                    assertNotNull( "The element for key [" + "key:" + test[i] + "] should not be null. i = " + i,
+		                                   element );
+		                    assertEquals( "Elements contents do not match expected", element.getVal(), value );
+		                }
+		            }
+		        }
+		        catch ( Exception e )
+		        {
+		            e.printStackTrace();
+		            fail( "Should not get an exception: " + e.toString() );
+		        }
+
+		        disk.removeAll();
+		    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexDiskCacheUnitTestAbstract.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexDiskCacheUnitTestAbstract.java
new file mode 100644
index 0000000..52975b0
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexDiskCacheUnitTestAbstract.java
@@ -0,0 +1,993 @@
+package org.apache.commons.jcs3.auxiliary.disk.indexed;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.auxiliary.MockCacheEventLogger;
+import org.apache.commons.jcs3.auxiliary.disk.DiskTestObject;
+import org.apache.commons.jcs3.utils.timing.SleepUtil;
+import org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDisk;
+import org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCache;
+import org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskElementDescriptor;
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.ElementAttributes;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+import org.apache.commons.jcs3.engine.control.group.GroupAttrName;
+import org.apache.commons.jcs3.engine.control.group.GroupId;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for common functionality.
+ * <p>
+ *
+ * @author Aaron Smuts
+ */
+public abstract class IndexDiskCacheUnitTestAbstract extends TestCase
+{
+    public abstract IndexedDiskCacheAttributes getCacheAttributes();
+
+    /**
+     * Simply verify that we can put items in the disk cache and retrieve them.
+     *
+     * @throws IOException
+     */
+    public void testSimplePutAndGet() throws IOException
+    {
+        IndexedDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName("testSimplePutAndGet");
+        cattr.setMaxKeySize(1000);
+        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTest");
+        IndexedDiskCache<String, String> disk = new IndexedDiskCache<>(cattr);
+
+        disk.processRemoveAll();
+
+        int cnt = 999;
+        for (int i = 0; i < cnt; i++)
+        {
+            IElementAttributes eAttr = new ElementAttributes();
+            eAttr.setIsSpool(true);
+            ICacheElement<String, String> element = new CacheElement<>("testSimplePutAndGet", "key:" + i, "data:" + i);
+            element.setElementAttributes(eAttr);
+            disk.processUpdate(element);
+        }
+
+        for (int i = 0; i < cnt; i++)
+        {
+            ICacheElement<String, String> element = disk.processGet("key:" + i);
+            assertNotNull("Should have received an element.", element);
+            assertEquals("Element is wrong.", "data:" + i, element.getVal());
+        }
+
+        // Test that getMultiple returns all the expected values
+        Set<String> keys = new HashSet<>();
+        for (int i = 0; i < cnt; i++)
+        {
+            keys.add("key:" + i);
+        }
+
+        Map<String, ICacheElement<String, String>> elements = disk.getMultiple(keys);
+        for (int i = 0; i < cnt; i++)
+        {
+            ICacheElement<String, String> element = elements.get("key:" + i);
+            assertNotNull("element " + i + ":key is missing", element);
+            assertEquals("value key:" + i, "data:" + i, element.getVal());
+        }
+        // System.out.println( disk.getStats() );
+    }
+
+    /**
+     * Add some items to the disk cache and then remove them one by one.
+     *
+     * @throws IOException
+     */
+    public void testRemoveItems() throws IOException
+    {
+        IndexedDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName("testRemoveItems");
+        cattr.setMaxKeySize(100);
+        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTest");
+        IndexedDiskCache<String, String> disk = new IndexedDiskCache<>(cattr);
+
+        disk.processRemoveAll();
+
+        int cnt = 25;
+        for (int i = 0; i < cnt; i++)
+        {
+            IElementAttributes eAttr = new ElementAttributes();
+            eAttr.setIsSpool(true);
+            ICacheElement<String, String> element = new CacheElement<>("testRemoveItems", "key:" + i, "data:" + i);
+            element.setElementAttributes(eAttr);
+            disk.processUpdate(element);
+        }
+
+        // remove each
+        for (int i = 0; i < cnt; i++)
+        {
+            disk.remove("key:" + i);
+            ICacheElement<String, String> element = disk.processGet("key:" + i);
+            assertNull("Should not have received an element.", element);
+        }
+    }
+
+    /**
+     * Verify that we don't override the largest item.
+     * <p>
+     *
+     * @throws IOException
+     */
+
+    /**
+     * Verify that the overlap check returns true when there are no overlaps.
+     */
+    public void testCheckForDedOverlaps_noOverlap()
+    {
+        // SETUP
+        IndexedDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName("testCheckForDedOverlaps_noOverlap");
+        cattr.setDiskPath("target/test-sandbox/UnitTest");
+        IndexedDiskCache<String, String> disk = new IndexedDiskCache<>(cattr);
+
+        int numDescriptors = 5;
+        int pos = 0;
+        IndexedDiskElementDescriptor[] sortedDescriptors = new IndexedDiskElementDescriptor[numDescriptors];
+        for (int i = 0; i < numDescriptors; i++)
+        {
+            IndexedDiskElementDescriptor descriptor = new IndexedDiskElementDescriptor(pos, i * 2);
+            pos = pos + (i * 2) + IndexedDisk.HEADER_SIZE_BYTES;
+            sortedDescriptors[i] = descriptor;
+        }
+
+        // DO WORK
+        boolean result = disk.checkForDedOverlaps(sortedDescriptors);
+
+        // VERIFY
+        assertTrue("There should be no overlap. it should be ok", result);
+    }
+
+    /**
+     * Verify that the overlap check returns false when there are overlaps.
+     */
+    public void testCheckForDedOverlaps_overlaps()
+    {
+        // SETUP
+        IndexedDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName("testCheckForDedOverlaps_overlaps");
+        cattr.setDiskPath("target/test-sandbox/UnitTest");
+        IndexedDiskCache<String, String> disk = new IndexedDiskCache<>(cattr);
+
+        int numDescriptors = 5;
+        int pos = 0;
+        IndexedDiskElementDescriptor[] sortedDescriptors = new IndexedDiskElementDescriptor[numDescriptors];
+        for (int i = 0; i < numDescriptors; i++)
+        {
+            IndexedDiskElementDescriptor descriptor = new IndexedDiskElementDescriptor(pos, i * 2);
+            // don't add the header + IndexedDisk.RECORD_HEADER;
+            pos = pos + (i * 2);
+            sortedDescriptors[i] = descriptor;
+        }
+
+        // DO WORK
+        boolean result = disk.checkForDedOverlaps(sortedDescriptors);
+
+        // VERIFY
+        assertFalse("There should be overlaps. it should be not ok", result);
+    }
+
+    /**
+     * Verify that the file size is as expected.
+     * <p>
+     *
+     * @throws IOException
+     * @throws InterruptedException
+     */
+    public void testFileSize() throws IOException, InterruptedException
+    {
+        // SETUP
+        IndexedDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName("testFileSize");
+        cattr.setDiskPath("target/test-sandbox/UnitTest");
+        IndexedDiskCache<Integer, DiskTestObject> disk = new IndexedDiskCache<>(cattr);
+
+        int numberToInsert = 20;
+        int bytes = 24;
+        ICacheElement<Integer, DiskTestObject>[] elements = DiskTestObjectUtil.createCacheElementsWithTestObjects(numberToInsert,
+            bytes, cattr.getCacheName());
+
+        for (int i = 0; i < elements.length; i++)
+        {
+            disk.processUpdate(elements[i]);
+        }
+
+        Thread.yield();
+        Thread.sleep(100);
+        Thread.yield();
+
+        long expectedSize = DiskTestObjectUtil.totalSize(elements, numberToInsert);
+        long resultSize = disk.getDataFileSize();
+
+        // System.out.println( "testFileSize stats " + disk.getStats() );
+
+        assertEquals("Wrong file size", expectedSize, resultSize);
+    }
+
+    /**
+     * Verify that items are added to the recycle bin on removal.
+     * <p>
+     *
+     * @throws IOException
+     * @throws InterruptedException
+     */
+    public void testRecyleBinSize() throws IOException, InterruptedException
+    {
+        // SETUP
+        int numberToInsert = 20;
+
+        IndexedDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName("testRecyleBinSize");
+        cattr.setDiskPath("target/test-sandbox/UnitTest");
+        cattr.setOptimizeAtRemoveCount(numberToInsert);
+        cattr.setMaxKeySize(numberToInsert * 2);
+        cattr.setMaxPurgatorySize(numberToInsert);
+        IndexedDiskCache<Integer, DiskTestObject> disk = new IndexedDiskCache<>(cattr);
+
+        int bytes = 1;
+        ICacheElement<Integer, DiskTestObject>[] elements = DiskTestObjectUtil.createCacheElementsWithTestObjects(numberToInsert,
+            bytes, cattr.getCacheName());
+
+        for (int i = 0; i < elements.length; i++)
+        {
+            disk.processUpdate(elements[i]);
+        }
+
+        Thread.yield();
+        Thread.sleep(100);
+        Thread.yield();
+
+        // remove half
+        int numberToRemove = elements.length / 2;
+        for (int i = 0; i < numberToRemove; i++)
+        {
+            disk.processRemove(elements[i].getKey());
+        }
+
+        // verify that the recycle bin has the correct amount.
+        assertEquals("The recycle bin should have the number removed.", numberToRemove, disk.getRecyleBinSize());
+    }
+
+    /**
+     * Verify that items of the same size use recycle bin spots. Setup the recycle bin by removing
+     * some items. Add some of the same size. Verify that the recycle count is the number added.
+     * <p>
+     *
+     * @throws IOException
+     * @throws InterruptedException
+     */
+    public void testRecyleBinUsage() throws IOException, InterruptedException
+    {
+        // SETUP
+        int numberToInsert = 20;
+
+        IndexedDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName("testRecyleBinUsage");
+        cattr.setDiskPath("target/test-sandbox/UnitTest");
+        cattr.setOptimizeAtRemoveCount(numberToInsert);
+        cattr.setMaxKeySize(numberToInsert * 2);
+        cattr.setMaxPurgatorySize(numberToInsert);
+        IndexedDiskCache<Integer, DiskTestObject> disk = new IndexedDiskCache<>(cattr);
+
+        // we will reuse these
+        int bytes = 1;
+        ICacheElement<Integer, DiskTestObject>[] elements = DiskTestObjectUtil.createCacheElementsWithTestObjects(numberToInsert,
+            bytes, cattr.getCacheName());
+
+        // Add some to the disk
+        for (int i = 0; i < elements.length; i++)
+        {
+            disk.processUpdate(elements[i]);
+        }
+
+        Thread.yield();
+        Thread.sleep(100);
+        Thread.yield();
+
+        // remove half of those added
+        int numberToRemove = elements.length / 2;
+        for (int i = 0; i < numberToRemove; i++)
+        {
+            disk.processRemove(elements[i].getKey());
+        }
+
+        // verify that the recycle bin has the correct amount.
+        assertEquals("The recycle bin should have the number removed.", numberToRemove, disk.getRecyleBinSize());
+
+        // add half as many as we removed. These should all use spots in the recycle bin.
+        int numberToAdd = numberToRemove / 2;
+        for (int i = 0; i < numberToAdd; i++)
+        {
+            disk.processUpdate(elements[i]);
+        }
+
+        // verify that we used the correct number of spots
+        assertEquals("The recycle bin should have the number removed." + disk.getStats(), numberToAdd, disk.getRecyleCount());
+    }
+
+    /**
+     * Verify that the data size is as expected after a remove and after a put that should use the
+     * spots.
+     * <p>
+     *
+     * @throws IOException
+     * @throws InterruptedException
+     */
+    public void testBytesFreeSize() throws IOException, InterruptedException
+    {
+        // SETUP
+        IndexedDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName("testBytesFreeSize");
+        cattr.setDiskPath("target/test-sandbox/UnitTest");
+        IndexedDiskCache<Integer, DiskTestObject> disk = new IndexedDiskCache<>(cattr);
+
+        int numberToInsert = 20;
+        int bytes = 24;
+        ICacheElement<Integer, DiskTestObject>[] elements = DiskTestObjectUtil.createCacheElementsWithTestObjects(numberToInsert,
+            bytes, cattr.getCacheName());
+
+        for (int i = 0; i < elements.length; i++)
+        {
+            disk.processUpdate(elements[i]);
+        }
+
+        Thread.yield();
+        Thread.sleep(100);
+        Thread.yield();
+
+        // remove half of those added
+        int numberToRemove = elements.length / 2;
+        for (int i = 0; i < numberToRemove; i++)
+        {
+            disk.processRemove(elements[i].getKey());
+        }
+
+        long expectedSize = DiskTestObjectUtil.totalSize(elements, numberToRemove);
+        long resultSize = disk.getBytesFree();
+
+        // System.out.println( "testBytesFreeSize stats " + disk.getStats() );
+
+        assertEquals("Wrong bytes free size" + disk.getStats(), expectedSize, resultSize);
+
+        // add half as many as we removed. These should all use spots in the recycle bin.
+        int numberToAdd = numberToRemove / 2;
+        for (int i = 0; i < numberToAdd; i++)
+        {
+            disk.processUpdate(elements[i]);
+        }
+
+        long expectedSize2 = DiskTestObjectUtil.totalSize(elements, numberToAdd);
+        long resultSize2 = disk.getBytesFree();
+        assertEquals("Wrong bytes free size" + disk.getStats(), expectedSize2, resultSize2);
+    }
+
+    /**
+     * Add some items to the disk cache and then remove them one by one.
+     * <p>
+     *
+     * @throws IOException
+     */
+    public void testRemove_PartialKey() throws IOException
+    {
+        IndexedDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName("testRemove_PartialKey");
+        cattr.setMaxKeySize(100);
+        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTest");
+        IndexedDiskCache<String, String> disk = new IndexedDiskCache<>(cattr);
+
+        disk.processRemoveAll();
+
+        int cnt = 25;
+        for (int i = 0; i < cnt; i++)
+        {
+            IElementAttributes eAttr = new ElementAttributes();
+            eAttr.setIsSpool(true);
+            ICacheElement<String, String> element = new CacheElement<>("testRemove_PartialKey", i + ":key", "data:"
+                + i);
+            element.setElementAttributes(eAttr);
+            disk.processUpdate(element);
+        }
+
+        // verif each
+        for (int i = 0; i < cnt; i++)
+        {
+            ICacheElement<String, String> element = disk.processGet(i + ":key");
+            assertNotNull("Shoulds have received an element.", element);
+        }
+
+        // remove each
+        for (int i = 0; i < cnt; i++)
+        {
+            disk.remove(i + ":");
+            ICacheElement<String, String> element = disk.processGet(i + ":key");
+            assertNull("Should not have received an element.", element);
+        }
+        // https://issues.apache.org/jira/browse/JCS-67
+        assertEquals("Recylenbin should not have more elements than we removed. Check for JCS-67", cnt, disk.getRecyleBinSize());
+    }
+
+    /**
+     * Verify that group members are removed if we call remove with a group.
+     *
+     * @throws IOException
+     */
+    public void testRemove_Group() throws IOException
+    {
+        // SETUP
+        IndexedDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName("testRemove_Group");
+        cattr.setMaxKeySize(100);
+        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTest");
+        IndexedDiskCache<GroupAttrName<String>, String> disk = new IndexedDiskCache<>(cattr);
+
+        disk.processRemoveAll();
+
+        String cacheName = "testRemove_Group_Region";
+        String groupName = "testRemove_Group";
+
+        int cnt = 25;
+        for (int i = 0; i < cnt; i++)
+        {
+            GroupAttrName<String> groupAttrName = getGroupAttrName(cacheName, groupName, i + ":key");
+            CacheElement<GroupAttrName<String>, String> element = new CacheElement<>(cacheName,
+                groupAttrName, "data:" + i);
+
+            IElementAttributes eAttr = new ElementAttributes();
+            eAttr.setIsSpool(true);
+            element.setElementAttributes(eAttr);
+
+            disk.processUpdate(element);
+        }
+
+        // verify each
+        for (int i = 0; i < cnt; i++)
+        {
+            GroupAttrName<String> groupAttrName = getGroupAttrName(cacheName, groupName, i + ":key");
+            ICacheElement<GroupAttrName<String>, String> element = disk.processGet(groupAttrName);
+            assertNotNull("Should have received an element.", element);
+        }
+
+        // DO WORK
+        // remove the group
+        disk.remove(getGroupAttrName(cacheName, groupName, null));
+
+        for (int i = 0; i < cnt; i++)
+        {
+            GroupAttrName<String> groupAttrName = getGroupAttrName(cacheName, groupName, i + ":key");
+            ICacheElement<GroupAttrName<String>, String> element = disk.processGet(groupAttrName);
+
+            // VERIFY
+            assertNull("Should not have received an element.", element);
+        }
+
+    }
+
+    /**
+     * Internal method used for group functionality.
+     * <p>
+     *
+     * @param cacheName
+     * @param group
+     * @param name
+     * @return GroupAttrName
+     */
+    private GroupAttrName<String> getGroupAttrName(String cacheName, String group, String name)
+    {
+        GroupId gid = new GroupId(cacheName, group);
+        return new GroupAttrName<>(gid, name);
+    }
+
+    /**
+     * Verify event log calls.
+     * <p>
+     *
+     * @throws Exception
+     */
+    public void testUpdate_EventLogging_simple() throws Exception
+    {
+        // SETUP
+        IndexedDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName("testUpdate_EventLogging_simple");
+        cattr.setMaxKeySize(100);
+        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTestCEL");
+        IndexedDiskCache<String, String> diskCache = new IndexedDiskCache<>(cattr);
+        diskCache.processRemoveAll();
+
+        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
+        diskCache.setCacheEventLogger(cacheEventLogger);
+
+        ICacheElement<String, String> item = new CacheElement<>("region", "key", "value");
+
+        // DO WORK
+        diskCache.update(item);
+
+        SleepUtil.sleepAtLeast(200);
+
+        // VERIFY
+        assertEquals("Start should have been called.", 1, cacheEventLogger.startICacheEventCalls);
+        assertEquals("End should have been called.", 1, cacheEventLogger.endICacheEventCalls);
+    }
+
+    /**
+     * Verify event log calls.
+     * <p>
+     *
+     * @throws Exception
+     */
+    public void testGet_EventLogging_simple() throws Exception
+    {
+        // SETUP
+        IndexedDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName("testGet_EventLogging_simple");
+        cattr.setMaxKeySize(100);
+        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTestCEL");
+        IndexedDiskCache<String, String> diskCache = new IndexedDiskCache<>(cattr);
+        diskCache.processRemoveAll();
+
+        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
+        diskCache.setCacheEventLogger(cacheEventLogger);
+
+        // DO WORK
+        diskCache.get("key");
+
+        // VERIFY
+        assertEquals("Start should have been called.", 1, cacheEventLogger.startICacheEventCalls);
+        assertEquals("End should have been called.", 1, cacheEventLogger.endICacheEventCalls);
+    }
+
+    /**
+     * Verify event log calls.
+     * <p>
+     *
+     * @throws Exception
+     */
+    public void testGetMultiple_EventLogging_simple() throws Exception
+    {
+        // SETUP
+        IndexedDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName("testGetMultiple_EventLogging_simple");
+        cattr.setMaxKeySize(100);
+        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTestCEL");
+        IndexedDiskCache<String, String> diskCache = new IndexedDiskCache<>(cattr);
+        diskCache.processRemoveAll();
+
+        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
+        diskCache.setCacheEventLogger(cacheEventLogger);
+
+        Set<String> keys = new HashSet<>();
+        keys.add("junk");
+
+        // DO WORK
+        diskCache.getMultiple(keys);
+
+        // VERIFY
+        // 1 for get multiple and 1 for get.
+        assertEquals("Start should have been called.", 2, cacheEventLogger.startICacheEventCalls);
+        assertEquals("End should have been called.", 2, cacheEventLogger.endICacheEventCalls);
+    }
+
+    /**
+     * Verify event log calls.
+     * <p>
+     *
+     * @throws Exception
+     */
+    public void testRemove_EventLogging_simple() throws Exception
+    {
+        // SETUP
+        IndexedDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName("testRemoveAll_EventLogging_simple");
+        cattr.setMaxKeySize(100);
+        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTestCEL");
+        IndexedDiskCache<String, String> diskCache = new IndexedDiskCache<>(cattr);
+        diskCache.processRemoveAll();
+
+        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
+        diskCache.setCacheEventLogger(cacheEventLogger);
+
+        // DO WORK
+        diskCache.remove("key");
+
+        // VERIFY
+        assertEquals("Start should have been called.", 1, cacheEventLogger.startICacheEventCalls);
+        assertEquals("End should have been called.", 1, cacheEventLogger.endICacheEventCalls);
+    }
+
+    /**
+     * Verify event log calls.
+     * <p>
+     *
+     * @throws Exception
+     */
+    public void testRemoveAll_EventLogging_simple() throws Exception
+    {
+        // SETUP
+        IndexedDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName("testRemoveAll_EventLogging_simple");
+        cattr.setMaxKeySize(100);
+        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTestCEL");
+        IndexedDiskCache<String, String> diskCache = new IndexedDiskCache<>(cattr);
+        diskCache.processRemoveAll();
+
+        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
+        diskCache.setCacheEventLogger(cacheEventLogger);
+
+        // DO WORK
+        diskCache.remove("key");
+
+        // VERIFY
+        assertEquals("Start should have been called.", 1, cacheEventLogger.startICacheEventCalls);
+        assertEquals("End should have been called.", 1, cacheEventLogger.endICacheEventCalls);
+    }
+
+    /**
+     * Test the basic get matching.
+     * <p>
+     *
+     * @throws Exception
+     */
+    public void testPutGetMatching_SmallWait() throws Exception
+    {
+        // SETUP
+        int items = 200;
+
+        String cacheName = "testPutGetMatching_SmallWait";
+        IndexedDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName(cacheName);
+        cattr.setMaxKeySize(100);
+        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTest");
+        IndexedDiskCache<String, String> diskCache = new IndexedDiskCache<>(cattr);
+
+        // DO WORK
+        for (int i = 0; i <= items; i++)
+        {
+            diskCache.update(new CacheElement<>(cacheName, i + ":key", cacheName + " data " + i));
+        }
+        Thread.sleep(500);
+
+        Map<String, ICacheElement<String, String>> matchingResults = diskCache.getMatching("1.8.+");
+
+        // VERIFY
+        assertEquals("Wrong number returned", 10, matchingResults.size());
+        // System.out.println( "matchingResults.keySet() " + matchingResults.keySet() );
+        // System.out.println( "\nAFTER TEST \n" + diskCache.getStats() );
+    }
+
+    /**
+     * Test the basic get matching. With no wait this will all come from purgatory.
+     * <p>
+     *
+     * @throws Exception
+     */
+    public void testPutGetMatching_NoWait() throws Exception
+    {
+        // SETUP
+        int items = 200;
+
+        String cacheName = "testPutGetMatching_NoWait";
+        IndexedDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName(cacheName);
+        cattr.setMaxKeySize(100);
+        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTest");
+        IndexedDiskCache<String, String> diskCache = new IndexedDiskCache<>(cattr);
+
+        // DO WORK
+        for (int i = 0; i <= items; i++)
+        {
+            diskCache.update(new CacheElement<>(cacheName, i + ":key", cacheName + " data " + i));
+        }
+
+        Map<String, ICacheElement<String, String>> matchingResults = diskCache.getMatching("1.8.+");
+
+        // VERIFY
+        assertEquals("Wrong number returned", 10, matchingResults.size());
+        // System.out.println( "matchingResults.keySet() " + matchingResults.keySet() );
+        // System.out.println( "\nAFTER TEST \n" + diskCache.getStats() );
+    }
+
+    /**
+     * Verify that the block disk cache can handle utf encoded strings.
+     * <p>
+     *
+     * @throws Exception
+     */
+    public void testUTF8String() throws Exception
+    {
+        String string = "IÒtÎrn‚tiÙn‡lizÊti¯n";
+        StringBuilder sb = new StringBuilder();
+        sb.append(string);
+        for (int i = 0; i < 4; i++)
+        {
+            sb.append(sb.toString()); // big string
+        }
+        string = sb.toString();
+
+        // System.out.println( "The string contains " + string.length() + " characters" );
+
+        String cacheName = "testUTF8String";
+
+        IndexedDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName(cacheName);
+        cattr.setMaxKeySize(100);
+        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTest");
+        IndexedDiskCache<String, String> diskCache = new IndexedDiskCache<>(cattr);
+
+        // DO WORK
+        diskCache.update(new CacheElement<>(cacheName, "x", string));
+
+        // VERIFY
+        assertNotNull(diskCache.get("x"));
+        Thread.sleep(1000);
+        ICacheElement<String, String> afterElement = diskCache.get("x");
+        assertNotNull(afterElement);
+        // System.out.println( "afterElement = " + afterElement );
+        String after = afterElement.getVal();
+
+        assertNotNull(after);
+        assertEquals("wrong string after retrieval", string, after);
+    }
+
+    /**
+     * Verify that the block disk cache can handle utf encoded strings.
+     * <p>
+     *
+     * @throws Exception
+     */
+    public void testUTF8ByteArray() throws Exception
+    {
+        String string = "IÒtÎrn‚tiÙn‡lizÊti¯n";
+        StringBuilder sb = new StringBuilder();
+        sb.append(string);
+        for (int i = 0; i < 4; i++)
+        {
+            sb.append(sb.toString()); // big string
+        }
+        string = sb.toString();
+        // System.out.println( "The string contains " + string.length() + " characters" );
+        byte[] bytes = string.getBytes(StandardCharsets.UTF_8);
+
+        String cacheName = "testUTF8ByteArray";
+
+        IndexedDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName(cacheName);
+        cattr.setMaxKeySize(100);
+        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTest");
+        IndexedDiskCache<String, byte[]> diskCache = new IndexedDiskCache<>(cattr);
+
+        // DO WORK
+        diskCache.update(new CacheElement<>(cacheName, "x", bytes));
+
+        // VERIFY
+        assertNotNull(diskCache.get("x"));
+        Thread.sleep(1000);
+        ICacheElement<String, byte[]> afterElement = diskCache.get("x");
+        assertNotNull(afterElement);
+        // System.out.println( "afterElement = " + afterElement );
+        byte[] after = afterElement.getVal();
+
+        assertNotNull(after);
+        assertEquals("wrong bytes after retrieval", string, new String(after, StandardCharsets.UTF_8));
+    }
+
+    /**
+     * Verify the item makes it to disk.
+     * <p>
+     *
+     * @throws IOException
+     */
+    public void testProcessUpdate_Simple() throws IOException
+    {
+        // SETUP
+        String cacheName = "testProcessUpdate_Simple";
+        IndexedDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName(cacheName);
+        cattr.setMaxKeySize(100);
+        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTest");
+        IndexedDiskCache<String, String> diskCache = new IndexedDiskCache<>(cattr);
+
+        String key = "myKey";
+        String value = "myValue";
+        ICacheElement<String, String> ce = new CacheElement<>(cacheName, key, value);
+
+        // DO WORK
+        diskCache.processUpdate(ce);
+        ICacheElement<String, String> result = diskCache.processGet(key);
+
+        // VERIFY
+        assertNotNull("Should have a result", result);
+        long fileSize = diskCache.getDataFileSize();
+        assertTrue("File should be greater than 0", fileSize > 0);
+    }
+
+    /**
+     * Verify the item makes it to disk.
+     * <p>
+     *
+     * @throws IOException
+     */
+    public void testProcessUpdate_SameKeySameSize() throws IOException
+    {
+        // SETUP
+        String cacheName = "testProcessUpdate_SameKeySameSize";
+        IndexedDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName(cacheName);
+        cattr.setMaxKeySize(100);
+        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTest");
+        IndexedDiskCache<String, String> diskCache = new IndexedDiskCache<>(cattr);
+
+        String key = "myKey";
+        String value = "myValue";
+        ICacheElement<String, String> ce1 = new CacheElement<>(cacheName, key, value);
+
+        // DO WORK
+        diskCache.processUpdate(ce1);
+        long fileSize1 = diskCache.getDataFileSize();
+
+        // DO WORK
+        ICacheElement<String, String> ce2 = new CacheElement<>(cacheName, key, value);
+        diskCache.processUpdate(ce2);
+        ICacheElement<String, String> result = diskCache.processGet(key);
+
+        // VERIFY
+        assertNotNull("Should have a result", result);
+        long fileSize2 = diskCache.getDataFileSize();
+        assertEquals("File should be the same", fileSize1, fileSize2);
+        int binSize = diskCache.getRecyleBinSize();
+        assertEquals("Should be nothing in the bin.", 0, binSize);
+    }
+
+    /**
+     * Verify the item makes it to disk.
+     * <p>
+     *
+     * @throws IOException
+     */
+    public void testProcessUpdate_SameKeySmallerSize() throws IOException
+    {
+        // SETUP
+        String cacheName = "testProcessUpdate_SameKeySmallerSize";
+        IndexedDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName(cacheName);
+        cattr.setMaxKeySize(100);
+        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTest");
+        IndexedDiskCache<String, String> diskCache = new IndexedDiskCache<>(cattr);
+
+        String key = "myKey";
+        String value = "myValue";
+        String value2 = "myValu";
+        ICacheElement<String, String> ce1 = new CacheElement<>(cacheName, key, value);
+
+        // DO WORK
+        diskCache.processUpdate(ce1);
+        long fileSize1 = diskCache.getDataFileSize();
+
+        // DO WORK
+        ICacheElement<String, String> ce2 = new CacheElement<>(cacheName, key, value2);
+        diskCache.processUpdate(ce2);
+        ICacheElement<String, String> result = diskCache.processGet(key);
+
+        // VERIFY
+        assertNotNull("Should have a result", result);
+        long fileSize2 = diskCache.getDataFileSize();
+        assertEquals("File should be the same", fileSize1, fileSize2);
+        int binSize = diskCache.getRecyleBinSize();
+        assertEquals("Should be nothing in the bin.", 0, binSize);
+    }
+
+    /**
+     * Verify that the old slot gets in the recycle bin.
+     * <p>
+     *
+     * @throws IOException
+     */
+    public void testProcessUpdate_SameKeyBiggerSize() throws IOException
+    {
+        // SETUP
+        String cacheName = "testProcessUpdate_SameKeyBiggerSize";
+        IndexedDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName(cacheName);
+        cattr.setMaxKeySize(100);
+        cattr.setDiskPath("target/test-sandbox/IndexDiskCacheUnitTest");
+        IndexedDiskCache<String, String> diskCache = new IndexedDiskCache<>(cattr);
+
+        String key = "myKey";
+        String value = "myValue";
+        String value2 = "myValue2";
+        ICacheElement<String, String> ce1 = new CacheElement<>(cacheName, key, value);
+
+        // DO WORK
+        diskCache.processUpdate(ce1);
+        long fileSize1 = diskCache.getDataFileSize();
+
+        // DO WORK
+        ICacheElement<String, String> ce2 = new CacheElement<>(cacheName, key, value2);
+        diskCache.processUpdate(ce2);
+        ICacheElement<String, String> result = diskCache.processGet(key);
+
+        // VERIFY
+        assertNotNull("Should have a result", result);
+        long fileSize2 = diskCache.getDataFileSize();
+        assertTrue("File should be greater.", fileSize1 < fileSize2);
+        int binSize = diskCache.getRecyleBinSize();
+        assertEquals("Should be one in the bin.", 1, binSize);
+    }
+
+    public void testLoadFromDisk() throws Exception
+    {
+        for (int i = 0; i < 15; i++)
+        { // usually after 2 time it fails
+            oneLoadFromDisk();
+        }
+    }
+
+    public void oneLoadFromDisk() throws Exception
+    {
+        // initialize object to be stored
+        String string = "IÒtÎrn‚tiÙn‡lizÊti¯n";
+        StringBuilder sb = new StringBuilder();
+        sb.append(string);
+        for (int i = 0; i < 4; i++)
+        {
+            sb.append(sb.toString()); // big string
+        }
+        string = sb.toString();
+
+        // initialize cache
+        String cacheName = "testLoadFromDisk";
+        IndexedDiskCacheAttributes cattr = getCacheAttributes();
+        cattr.setCacheName(cacheName);
+        cattr.setMaxKeySize(100);
+        cattr.setDiskPath("target/test-sandbox/BlockDiskCacheUnitTest");
+        IndexedDiskCache<String, String> diskCache = new IndexedDiskCache<>(cattr);
+
+        // DO WORK
+        for (int i = 0; i < 50; i++)
+        {
+            diskCache.update(new CacheElement<>(cacheName, "x" + i, string));
+        }
+        // Thread.sleep(1000);
+        // VERIFY
+        diskCache.dispose();
+        // Thread.sleep(1000);
+
+        diskCache = new IndexedDiskCache<>(cattr);
+
+        for (int i = 0; i < 50; i++)
+        {
+            ICacheElement<String, String> afterElement = diskCache.get("x" + i);
+            assertNotNull("Missing element from cache. Cache size: " + diskCache.getSize() + " element: x" + i, afterElement);
+            assertEquals("wrong string after retrieval", string, afterElement.getVal());
+        }
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheConcurrentNoDeadLockUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheConcurrentNoDeadLockUnitTest.java
new file mode 100644
index 0000000..8fce991
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheConcurrentNoDeadLockUnitTest.java
@@ -0,0 +1,149 @@
+package org.apache.commons.jcs3.auxiliary.disk.indexed;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.engine.control.CompositeCacheManager;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.extensions.ActiveTestSuite;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.textui.TestRunner;
+
+/**
+ * Test which exercises the indexed disk cache. Runs three threads against the
+ * same region.
+ *
+ * @version $Id: TestDiskCacheConcurrentForDeadLock.java,v 1.2 2005/02/01
+ *          00:01:59 asmuts Exp $
+ */
+public class IndexedDiskCacheConcurrentNoDeadLockUnitTest
+    extends TestCase
+{
+    /**
+     * Constructor for the TestDiskCache object.
+     *
+     * @param testName
+     */
+    public IndexedDiskCacheConcurrentNoDeadLockUnitTest( String testName )
+    {
+        super( testName );
+    }
+
+    /**
+     * Main method passes this test to the text test runner.
+     *
+     * @param args
+     */
+    public static void main( String args[] )
+    {
+        String[] testCaseName = { IndexedDiskCacheConcurrentNoDeadLockUnitTest.class.getName() };
+        TestRunner.main( testCaseName );
+    }
+
+    /**
+     * A unit test suite for JUnit
+     *
+     * @return The test suite
+     */
+    public static Test suite()
+    {
+        ActiveTestSuite suite = new ActiveTestSuite();
+
+        suite.addTest( new IndexedDiskCacheRandomConcurrentTestUtil( "testIndexedDiskCache1" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "indexedRegion4", 1, 200, 1 );
+            }
+        } );
+
+        suite.addTest( new IndexedDiskCacheRandomConcurrentTestUtil( "testIndexedDiskCache2" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "indexedRegion4", 10000, 50000, 2 );
+            }
+        } );
+
+        suite.addTest( new IndexedDiskCacheRandomConcurrentTestUtil( "testIndexedDiskCache3" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "indexedRegion4", 10000, 50000, 3 );
+            }
+        } );
+
+        suite.addTest( new IndexedDiskCacheRandomConcurrentTestUtil( "testIndexedDiskCache4" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "indexedRegion4", 10000, 50000, 4 );
+            }
+        } );
+
+        suite.addTest( new IndexedDiskCacheRandomConcurrentTestUtil( "testIndexedDiskCache5" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "indexedRegion4", 10000, 50000, 5 );
+            }
+        } );
+
+        return suite;
+    }
+
+    /**
+     * Test setup
+     */
+    @Override
+    public void setUp()
+    {
+        JCS.setConfigFilename( "/TestDiskCacheCon.ccf" );
+    }
+
+    /**
+     * Test tearDown. Dispose of the cache.
+     */
+    @Override
+    public void tearDown()
+    {
+        try
+        {
+            CompositeCacheManager cacheMgr = CompositeCacheManager.getInstance();
+            cacheMgr.shutDown();
+        }
+        catch ( Exception e )
+        {
+            // log.error(e);
+        }
+    }
+
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheConcurrentUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheConcurrentUnitTest.java
new file mode 100644
index 0000000..c0efe32
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheConcurrentUnitTest.java
@@ -0,0 +1,252 @@
+package org.apache.commons.jcs3.auxiliary.disk.indexed;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.extensions.ActiveTestSuite;
+import junit.framework.Test;
+import junit.framework.TestCase;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+
+/**
+ * Test which exercises the indexed disk cache. This one uses three different
+ * regions for thre threads.
+ *
+ * @version $Id$
+ */
+public class IndexedDiskCacheConcurrentUnitTest
+    extends TestCase
+{
+    /**
+     * Number of items to cache, twice the configured maxObjects for the memory
+     * cache regions.
+     */
+    private static int items = 200;
+
+    /**
+     * Constructor for the TestDiskCache object.
+     *
+     * @param testName
+     */
+    public IndexedDiskCacheConcurrentUnitTest( String testName )
+    {
+        super( testName );
+    }
+
+    /**
+     * Main method passes this test to the text test runner.
+     *
+     * @param args
+     */
+    public static void main( String args[] )
+    {
+        String[] testCaseName = { IndexedDiskCacheConcurrentUnitTest.class.getName() };
+        junit.textui.TestRunner.main( testCaseName );
+    }
+
+    /**
+     * A unit test suite for JUnit
+     *
+     * @return The test suite
+     */
+    public static Test suite()
+    {
+        ActiveTestSuite suite = new ActiveTestSuite();
+
+        suite.addTest( new IndexedDiskCacheConcurrentUnitTest( "testIndexedDiskCache1" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "indexedRegion1" );
+            }
+        } );
+
+        suite.addTest( new IndexedDiskCacheConcurrentUnitTest( "testIndexedDiskCache2" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "indexedRegion2" );
+            }
+        } );
+
+        suite.addTest( new IndexedDiskCacheConcurrentUnitTest( "testIndexedDiskCache3" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "indexedRegion3" );
+            }
+        } );
+
+        suite.addTest( new IndexedDiskCacheConcurrentUnitTest( "testIndexedDiskCache4" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegionInRange( "indexedRegion3", 300, 600 );
+            }
+        } );
+
+        return suite;
+    }
+
+    /**
+     * Test setup
+     */
+    @Override
+    public void setUp()
+    {
+        JCS.setConfigFilename( "/TestDiskCache.ccf" );
+    }
+
+    /**
+     * Adds items to cache, gets them, and removes them. The item count is more
+     * than the size of the memory cache, so items should spool to disk.
+     *
+     * @param region
+     *            Name of the region to access
+     *
+     * @throws Exception
+     *                If an error occurs
+     */
+    public void runTestForRegion( String region )
+        throws Exception
+    {
+        CacheAccess<String, String> jcs = JCS.getInstance( region );
+
+        // Add items to cache
+        for ( int i = 0; i <= items; i++ )
+        {
+            jcs.put( i + ":key", region + " data " + i );
+        }
+
+        // Test that all items are in cache
+        for ( int i = 0; i <= items; i++ )
+        {
+            String value = jcs.get( i + ":key" );
+
+            assertEquals( region + " data " + i, value );
+        }
+
+        // Test that getElements returns all the expected values
+        Set<String> keys = new HashSet<>();
+        for ( int i = 0; i <= items; i++ )
+        {
+            keys.add( i + ":key" );
+        }
+
+        Map<String, ICacheElement<String, String>> elements = jcs.getCacheElements( keys );
+        for ( int i = 0; i <= items; i++ )
+        {
+            ICacheElement<String, String> element = elements.get( i + ":key" );
+            assertNotNull( "element " + i + ":key is missing", element );
+            assertEquals( "value " + i + ":key", region + " data " + i, element.getVal() );
+        }
+
+        // Remove all the items
+        for ( int i = 0; i <= items; i++ )
+        {
+            jcs.remove( i + ":key" );
+        }
+
+        // Verify removal
+        // another thread may have inserted since
+        for ( int i = 0; i <= items; i++ )
+        {
+            assertNull( "Removed key should be null: " + i + ":key" + "\n stats " + jcs.getStats(), jcs
+                .get( i + ":key" ) );
+        }
+    }
+
+    /**
+     * Adds items to cache, gets them, and removes them. The item count is more
+     * than the size of the memory cache, so items should spool to disk.
+     *
+     * @param region
+     *            Name of the region to access
+     * @param start
+     * @param end
+     *
+     * @throws Exception
+     *                If an error occurs
+     */
+    public void runTestForRegionInRange( String region, int start, int end )
+        throws Exception
+    {
+        CacheAccess<String, String> jcs = JCS.getInstance( region );
+
+        // Add items to cache
+        for ( int i = start; i <= end; i++ )
+        {
+            jcs.put( i + ":key", region + " data " + i );
+        }
+
+        // Test that all items are in cache
+        for ( int i = start; i <= end; i++ )
+        {
+            String value = jcs.get( i + ":key" );
+
+            assertEquals( region + " data " + i, value );
+        }
+
+        // Test that getElements returns all the expected values
+        Set<String> keys = new HashSet<>();
+        for ( int i = start; i <= end; i++ )
+        {
+            keys.add( i + ":key" );
+        }
+
+        Map<String, ICacheElement<String, String>> elements = jcs.getCacheElements( keys );
+        for ( int i = start; i <= end; i++ )
+        {
+            ICacheElement<String, String> element = elements.get( i + ":key" );
+            assertNotNull( "element " + i + ":key is missing", element );
+            assertEquals( "value " + i + ":key", region + " data " + i, element.getVal() );
+        }
+
+        // Remove all the items
+        for ( int i = start; i <= end; i++ )
+        {
+            jcs.remove( i + ":key" );
+        }
+
+//        System.out.println( jcs.getStats() );
+
+        // Verify removal
+        // another thread may have inserted since
+        for ( int i = start; i <= end; i++ )
+        {
+            assertNull( "Removed key should be null: " + i + ":key " + "\n stats " + jcs.getStats(), jcs.get( i
+                + ":key" ) );
+        }
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheDefragPerformanceTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheDefragPerformanceTest.java
new file mode 100644
index 0000000..d64f88f
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheDefragPerformanceTest.java
@@ -0,0 +1,181 @@
+package org.apache.commons.jcs3.auxiliary.disk.indexed;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+import java.io.Serializable;
+import java.text.DecimalFormat;
+import java.util.Random;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+
+/**
+ * This is for manually testing the defrag process.
+ */
+public class IndexedDiskCacheDefragPerformanceTest
+    extends TestCase
+{
+    /** For readability */
+    private static final String LOG_DIVIDER = "---------------------------";
+
+    /** total to test with */
+    private static final int TOTAL_ELEMENTS = 30000;
+
+    /** time to wait */
+    private static final long SLEEP_TIME_DISK = 8000;
+
+    /** how often to log */
+    private static final int LOG_INCREMENT = 5000;
+
+    /** for getting memory usage */
+    private static Runtime rt = Runtime.getRuntime();
+
+    /** for displaying memory usage */
+    private static DecimalFormat format = new DecimalFormat( "#,###" );
+
+    /**
+     * @throws Exception
+     */
+    public void testRealTimeOptimization()
+        throws Exception
+    {
+        System.out.println( LOG_DIVIDER );
+        System.out.println( "JCS DEFRAG PERFORMANCE TESTS" );
+        System.out.println( LOG_DIVIDER );
+        logMemoryUsage();
+        IndexedDiskCacheDefragPerformanceTest.runRealTimeOptimizationTest();
+        logMemoryUsage();
+
+        System.out.println( LOG_DIVIDER );
+    }
+
+    /**
+     * @throws Exception
+     */
+    private static void runRealTimeOptimizationTest()
+        throws Exception
+    {
+        JCS.setConfigFilename( "/TestDiskCacheDefragPerformance.ccf" );
+        CacheAccess<Integer, Tile> jcs = JCS.getInstance( "defrag" );
+
+        Tile tile;
+        System.out.println( "Cache Defrag Test" );
+
+        Random random = new Random( 89 );
+        for ( int i = 0; i < TOTAL_ELEMENTS; i++ )
+        {
+            int bytes = random.nextInt( 20 );
+            // 4-24 KB
+            tile = new Tile( Integer.valueOf( i ), new byte[( bytes + 4 ) * 1024] );
+            // images
+
+            jcs.put( tile.id, tile );
+
+            if ( ( i != 0 ) && ( 0 == ( i % 100 ) ) )
+            {
+                jcs.get( Integer.valueOf( random.nextInt( i ) ) );
+            }
+
+            if ( 0 == ( i % LOG_INCREMENT ) )
+            {
+                System.out.print( i + ", " );
+                Thread.sleep( SLEEP_TIME_DISK );
+            }
+        }
+
+        System.out.println( LOG_DIVIDER );
+        System.out.println( "Total elements = " + TOTAL_ELEMENTS );
+        System.out.println( "Stats prior to sleeping " + jcs.getStats() );
+
+        // Allow system to settle down
+        System.out.println( "Sleeping for a a minute." );
+        Thread.sleep( 60000 );
+
+        System.out.println( LOG_DIVIDER );
+        System.out.println( "Stats prior to dispose " + jcs.getStats() );
+
+        jcs.dispose();
+        System.out.println( LOG_DIVIDER );
+        System.out.println( "Stats after dispose " + jcs.getStats() );
+        System.out.println( "Done testing." );
+    }
+
+    /**
+     * Logs the memory usage.
+     */
+    private static void logMemoryUsage()
+    {
+        long byte2MB = 1024 * 1024;
+        long total = rt.totalMemory() / byte2MB;
+        long free = rt.freeMemory() / byte2MB;
+        long used = total - free;
+        System.out.println( LOG_DIVIDER );
+        System.out.println( "Memory:" + " Used:" + format.format( used ) + "MB" + " Free:" + format.format( free )
+            + "MB" + " Total:" + format.format( total ) + "MB" );
+    }
+
+    /**
+     * Resembles a cached image.
+     */
+    private static class Tile
+        implements Serializable
+    {
+        /** Don't change */
+        private static final long serialVersionUID = 1L;
+
+        /**
+         * Key
+         */
+        public Integer id;
+
+        /**Byte size
+         *
+         */
+        public byte[] imageBytes;
+
+        /**
+         * @param id
+         * @param imageBytes
+         */
+        public Tile( Integer id, byte[] imageBytes )
+        {
+            this.id = id;
+            this.imageBytes = imageBytes;
+        }
+    }
+
+    /**
+     * @param args
+     */
+    public static void main( String args[] )
+    {
+        try
+        {
+            IndexedDiskCacheDefragPerformanceTest tester = new IndexedDiskCacheDefragPerformanceTest();
+            tester.testRealTimeOptimization();
+        }
+        catch ( Exception e )
+        {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheKeyStoreUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheKeyStoreUnitTest.java
new file mode 100644
index 0000000..04677ba
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheKeyStoreUnitTest.java
@@ -0,0 +1,152 @@
+package org.apache.commons.jcs3.auxiliary.disk.indexed;
+
+import org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCache;
+import org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes;
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.ElementAttributes;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/**
+ * Test store and load keys.
+ *
+ * @author Aaron Smuts
+ *
+ */
+public class IndexedDiskCacheKeyStoreUnitTest
+    extends TestCase
+{
+
+    /**
+     * Add some keys, store them, load them from disk, then check to see that we
+     * can get the items.
+     *
+     * @throws Exception
+     *
+     */
+    public void testStoreKeys()
+        throws Exception
+    {
+        IndexedDiskCacheAttributes cattr = new IndexedDiskCacheAttributes();
+        cattr.setCacheName( "testStoreKeys" );
+        cattr.setMaxKeySize( 100 );
+        cattr.setDiskPath( "target/test-sandbox/KeyStoreUnitTest" );
+        IndexedDiskCache<String, String> disk = new IndexedDiskCache<>( cattr );
+
+        disk.processRemoveAll();
+
+        int cnt = 25;
+        for ( int i = 0; i < cnt; i++ )
+        {
+            IElementAttributes eAttr = new ElementAttributes();
+            eAttr.setIsSpool( true );
+            ICacheElement<String, String> element = new CacheElement<>( cattr.getCacheName(), "key:" + i, "data:" + i );
+            element.setElementAttributes( eAttr );
+            disk.processUpdate( element );
+        }
+
+        for ( int i = 0; i < cnt; i++ )
+        {
+            ICacheElement<String, String> element = disk.processGet( "key:" + i );
+            assertNotNull( "presave, Should have received an element.", element );
+            assertEquals( "presave, element is wrong.", "data:" + i, element.getVal() );
+        }
+
+        disk.saveKeys();
+
+        disk.loadKeys();
+
+        assertEquals( "The disk is the wrong size.", cnt, disk.getSize() );
+
+        for ( int i = 0; i < cnt; i++ )
+        {
+            ICacheElement<String, String> element = disk.processGet( "key:" + i );
+            assertNotNull( "postsave, Should have received an element.", element );
+            assertEquals( "postsave, element is wrong.", "data:" + i, element.getVal() );
+        }
+
+        disk.dump();
+
+    }
+
+
+    /**
+     * Add some elements, remove 1, call optimize, verify that the removed isn't present.
+     *
+     * We should also compare the data file sizes. . . .
+     *
+     * @throws Exception
+     *
+     */
+    public void testOptiimize()
+        throws Exception
+    {
+        IndexedDiskCacheAttributes cattr = new IndexedDiskCacheAttributes();
+        cattr.setCacheName( "testOptimize" );
+        cattr.setMaxKeySize( 100 );
+        cattr.setDiskPath( "target/test-sandbox/KeyStoreUnitTest" );
+        IndexedDiskCache<String, String> disk = new IndexedDiskCache<>( cattr );
+
+        disk.processRemoveAll();
+
+        int cnt = 25;
+        for ( int i = 0; i < cnt; i++ )
+        {
+            IElementAttributes eAttr = new ElementAttributes();
+            eAttr.setIsSpool( true );
+            ICacheElement<String, String> element = new CacheElement<>( cattr.getCacheName(), "key:" + i, "data:" + i );
+            element.setElementAttributes( eAttr );
+            disk.processUpdate( element );
+        }
+
+        long preAddRemoveSize = disk.getDataFileSize();
+
+        IElementAttributes eAttr = new ElementAttributes();
+        eAttr.setIsSpool( true );
+        ICacheElement<String, String> elementSetup = new CacheElement<>( cattr.getCacheName(), "key:" + "A", "data:" + "A" );
+        elementSetup.setElementAttributes( eAttr );
+        disk.processUpdate( elementSetup );
+
+        ICacheElement<String, String> elementRet = disk.processGet( "key:" + "A" );
+        assertNotNull( "postsave, Should have received an element.", elementRet );
+        assertEquals( "postsave, element is wrong.", "data:" + "A", elementRet.getVal() );
+
+        disk.remove( "key:" + "A" );
+
+        long preSize = disk.getDataFileSize();
+        // synchronous versoin
+        disk.optimizeFile(); //deoptimizeRealTime();
+        long postSize = disk.getDataFileSize();
+
+        assertTrue( "Should be smaller. postsize="+postSize+" preSize="+preSize, postSize < preSize );
+        assertEquals( "Should be the same size after optimization as before add and remove.", preAddRemoveSize, postSize );
+
+        for ( int i = 0; i < cnt; i++ )
+        {
+            ICacheElement<String, String> element = disk.processGet( "key:" + i );
+            assertNotNull( "postsave, Should have received an element.", element );
+            assertEquals( "postsave, element is wrong.", "data:" + i, element.getVal() );
+        }
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheNoMemoryUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheNoMemoryUnitTest.java
new file mode 100644
index 0000000..ed63a59
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheNoMemoryUnitTest.java
@@ -0,0 +1,179 @@
+package org.apache.commons.jcs3.auxiliary.disk.indexed;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.extensions.ActiveTestSuite;
+import junit.framework.Test;
+import junit.framework.TestCase;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+
+/**
+ * Test which exercises the indexed disk cache. This one uses three different
+ * regions for thre threads. It uses a config file that specifies 0 items in
+ * memory.
+ */
+public class IndexedDiskCacheNoMemoryUnitTest
+    extends TestCase
+{
+    /**
+     * Number of items to cache; the configured maxObjects for the memory cache
+     * regions is 0.
+     */
+    private static int items = 2000;
+
+    /**
+     * @param testName
+     */
+    public IndexedDiskCacheNoMemoryUnitTest( String testName )
+    {
+        super( testName );
+    }
+
+    /**
+     * Main method passes this test to the text test runner.
+     * <p>
+     * @param args
+     */
+    public static void main( String args[] )
+    {
+        String[] testCaseName = { IndexedDiskCacheNoMemoryUnitTest.class.getName() };
+        junit.textui.TestRunner.main( testCaseName );
+    }
+
+    /**
+     * A unit test suite for JUnit
+     * <p>
+     * @return The test suite
+     */
+    public static Test suite()
+    {
+        ActiveTestSuite suite = new ActiveTestSuite();
+
+        suite.addTest( new IndexedDiskCacheNoMemoryUnitTest( "testIndexedDiskCache1" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "indexedRegion1" );
+            }
+        } );
+
+        suite.addTest( new IndexedDiskCacheNoMemoryUnitTest( "testIndexedDiskCache2" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "indexedRegion2" );
+            }
+        } );
+
+        suite.addTest( new IndexedDiskCacheNoMemoryUnitTest( "testIndexedDiskCache3" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "indexedRegion3" );
+            }
+        } );
+
+        return suite;
+    }
+
+    /**
+     * Test setup
+     */
+    @Override
+    public void setUp()
+    {
+        JCS.setConfigFilename( "/TestDiskCacheNoMemory.ccf" );
+    }
+
+    /**
+     * Adds items to cache, gets them, and removes them. The item count is more
+     * than the size of the memory cache, so items should spool to disk.
+     *
+     * @param region
+     *            Name of the region to access
+     *
+     * @throws Exception
+     *                If an error occurs
+     */
+    public void runTestForRegion( String region )
+        throws Exception
+    {
+        CacheAccess<String, String> jcs = JCS.getInstance( region );
+
+        // Add items to cache
+
+        for ( int i = 0; i <= items; i++ )
+        {
+            jcs.put( i + ":key", region + " data " + i );
+        }
+
+        // Test that all items are in cache
+
+        for ( int i = 0; i <= items; i++ )
+        {
+            String value = jcs.get( i + ":key" );
+
+            assertEquals( region + " data " + i, value );
+        }
+
+        // Test that getElements returns all the expected values
+        Set<String> keys = new HashSet<>();
+        for ( int i = 0; i <= items; i++ )
+        {
+            keys.add( i + ":key" );
+        }
+
+        Map<String, ICacheElement<String, String>> elements = jcs.getCacheElements( keys );
+        for ( int i = 0; i <= items; i++ )
+        {
+            ICacheElement<String, String> element = elements.get( i + ":key" );
+            assertNotNull( "element " + i + ":key is missing", element );
+            assertEquals( "value " + i + ":key", region + " data " + i, element.getVal() );
+        }
+
+        // Remove all the items
+        for ( int i = 0; i <= items; i++ )
+        {
+            jcs.remove( i + ":key" );
+        }
+
+        // Verify removal
+        for ( int i = 0; i <= items; i++ )
+        {
+            assertNull( "Removed key should be null: " + i + ":key" + "\n stats " + jcs.getStats(), jcs.get( i + ":key" ) );
+        }
+
+        // dump the stats to the report
+//        System.out.println( jcs.getStats() );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheOptimizationUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheOptimizationUnitTest.java
new file mode 100644
index 0000000..5971ef8
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheOptimizationUnitTest.java
@@ -0,0 +1,97 @@
+package org.apache.commons.jcs3.auxiliary.disk.indexed;
+
+import org.apache.commons.jcs3.auxiliary.disk.DiskTestObject;
+import org.apache.commons.jcs3.utils.timing.SleepUtil;
+import org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCache;
+import org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for the optimization routine.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class IndexedDiskCacheOptimizationUnitTest
+    extends TestCase
+{
+    /**
+     * Set the optimize at remove count to 10. Add 20. Check the file size. Remove 10. Check the
+     * times optimized. Check the file size.
+     * @throws Exception
+     */
+    public void testBasicOptimization()
+        throws Exception
+    {
+        // SETUP
+        int removeCount = 50;
+
+        IndexedDiskCacheAttributes cattr = new IndexedDiskCacheAttributes();
+        cattr.setCacheName( "testOptimization" );
+        cattr.setMaxKeySize( removeCount * 2 );
+        cattr.setOptimizeAtRemoveCount( removeCount );
+        cattr.setDiskPath( "target/test-sandbox/testOptimization" );
+        IndexedDiskCache<Integer, DiskTestObject> disk = new IndexedDiskCache<>( cattr );
+
+        disk.removeAll();
+
+        int numberToInsert = removeCount * 3;
+        ICacheElement<Integer, DiskTestObject>[] elements = DiskTestObjectUtil
+            .createCacheElementsWithTestObjectsOfVariableSizes( numberToInsert, cattr.getCacheName() );
+
+        for ( int i = 0; i < elements.length; i++ )
+        {
+            disk.processUpdate( elements[i] );
+        }
+
+
+        Thread.sleep( 1000 );
+        long sizeBeforeRemove = disk.getDataFileSize();
+        // System.out.println( "file sizeBeforeRemove " + sizeBeforeRemove );
+        // System.out.println( "totalSize inserted " + DiskTestObjectUtil.totalSize( elements, numberToInsert ) );
+
+        // DO WORK
+        for ( int i = 0; i < removeCount; i++ )
+        {
+            disk.processRemove( Integer.valueOf( i ) );
+        }
+
+        SleepUtil.sleepAtLeast( 1000 );
+
+        disk.optimizeFile();
+        // VERIFY
+        long sizeAfterRemove = disk.getDataFileSize();
+        long expectedSizeAfterRemove = DiskTestObjectUtil.totalSize( elements, removeCount, elements.length );
+
+        // test is prone to failure for timing reasons.
+        if ( expectedSizeAfterRemove != sizeAfterRemove )
+        {
+            SleepUtil.sleepAtLeast( 2000 );
+        }
+
+        assertTrue( "The post optimization size should be smaller."
+                +"sizeAfterRemove=" + sizeAfterRemove + " sizeBeforeRemove= " +sizeBeforeRemove
+                , sizeAfterRemove < sizeBeforeRemove );
+        assertEquals( "The file size is not as expected size.", expectedSizeAfterRemove, sizeAfterRemove );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheRandomConcurrentTestUtil.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheRandomConcurrentTestUtil.java
new file mode 100644
index 0000000..a557892
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheRandomConcurrentTestUtil.java
@@ -0,0 +1,87 @@
+package org.apache.commons.jcs3.auxiliary.disk.indexed;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+import org.apache.commons.jcs3.access.TestCacheAccess;
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+
+/**
+ * This is used by other tests to generate a random load on the disk cache.
+ */
+public class IndexedDiskCacheRandomConcurrentTestUtil
+    extends TestCase
+{
+
+    /**
+     * Constructor for the TestDiskCache object.
+     *
+     * @param testName
+     */
+    public IndexedDiskCacheRandomConcurrentTestUtil( String testName )
+    {
+        super( testName );
+    }
+
+    /**
+     * Randomly adds items to cache, gets them, and removes them. The range
+     * count is more than the size of the memory cache, so items should spool to
+     * disk.
+     *
+     * @param region
+     *            Name of the region to access
+     * @param range
+     * @param numOps
+     * @param testNum
+     *
+     * @throws Exception
+     *                If an error occurs
+     */
+    public void runTestForRegion( String region, int range, int numOps, int testNum )
+        throws Exception
+    {
+        // run a rondom operation test to detect deadlocks
+        TestCacheAccess tca = new TestCacheAccess( "/TestDiskCacheCon.ccf" );
+        tca.setRegion( region );
+        tca.random( range, numOps );
+
+        // make sure a simple put then get works
+        // this may fail if the other tests are flooding the disk cache
+        CacheAccess<String, String> jcs = JCS.getInstance( region );
+        String key = "testKey" + testNum;
+        String data = "testData" + testNum;
+        jcs.put( key, data );
+        String value = jcs.get( key );
+        assertEquals( data, value );
+
+    }
+
+    /**
+     * Test setup
+     */
+    @Override
+    public void setUp()
+    {
+        JCS.setConfigFilename( "/TestDiskCacheCon.ccf" );
+    }
+
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheSameRegionConcurrentUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheSameRegionConcurrentUnitTest.java
new file mode 100644
index 0000000..21fd45a
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheSameRegionConcurrentUnitTest.java
@@ -0,0 +1,209 @@
+package org.apache.commons.jcs3.auxiliary.disk.indexed;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.extensions.ActiveTestSuite;
+import junit.framework.Test;
+import junit.framework.TestCase;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+
+/**
+ * Test which exercises the indexed disk cache. Runs three threads against the
+ * same region.
+ */
+public class IndexedDiskCacheSameRegionConcurrentUnitTest
+    extends TestCase
+{
+    /**
+     * Constructor for the TestDiskCache object.
+     *
+     * @param testName
+     */
+    public IndexedDiskCacheSameRegionConcurrentUnitTest( String testName )
+    {
+        super( testName );
+    }
+
+    /**
+     * Main method passes this test to the text test runner.
+     *
+     * @param args
+     */
+    public static void main( String args[] )
+    {
+        String[] testCaseName = { IndexedDiskCacheSameRegionConcurrentUnitTest.class.getName() };
+        junit.textui.TestRunner.main( testCaseName );
+    }
+
+    /**
+     * A unit test suite for JUnit
+     *
+     * @return The test suite
+     */
+    public static Test suite()
+    {
+        ActiveTestSuite suite = new ActiveTestSuite();
+
+        suite.addTest( new IndexedDiskCacheSameRegionConcurrentUnitTest( "testIndexedDiskCache1" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "indexedRegion4", 0, 200 );
+            }
+        } );
+
+        suite.addTest( new IndexedDiskCacheSameRegionConcurrentUnitTest( "testIndexedDiskCache2" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "indexedRegion4", 1000, 1200 );
+            }
+        } );
+
+        suite.addTest( new IndexedDiskCacheSameRegionConcurrentUnitTest( "testIndexedDiskCache3" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "indexedRegion4", 2000, 2200 );
+            }
+        } );
+
+        suite.addTest( new IndexedDiskCacheSameRegionConcurrentUnitTest( "testIndexedDiskCache4" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "indexedRegion4", 2200, 5200 );
+            }
+        } );
+
+        suite.addTest( new IndexedDiskCacheSameRegionConcurrentUnitTest( "testIndexedDiskCache5" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "indexedRegion4", 0, 5100 );
+            }
+        } );
+
+        return suite;
+    }
+
+    /**
+     * Test setup
+     */
+    @Override
+    public void setUp()
+    {
+        JCS.setConfigFilename( "/TestDiskCacheCon.ccf" );
+    }
+
+    // /**
+    // * Tests the region which uses the indexed disk cache
+    // */
+    // public void testIndexedDiskCache()
+    // throws Exception
+    // {
+    // runTestForRegion( "indexedRegion" );
+    // }
+    //
+    // /**
+    // * Tests the region which uses the indexed disk cache
+    // */
+    // public void testIndexedDiskCache2()
+    // throws Exception
+    // {
+    // runTestForRegion( "indexedRegion2" );
+    // }
+
+    /**
+     * Adds items to cache, gets them, and removes them. The item count is more
+     * than the size of the memory cache, so items should spool to disk.
+     *
+     * @param region
+     *            Name of the region to access
+     * @param start
+     * @param end
+     *
+     * @throws Exception
+     *                If an error occurs
+     */
+    public void runTestForRegion( String region, int start, int end )
+        throws Exception
+    {
+        CacheAccess<String, String> jcs = JCS.getInstance( region );
+
+        // Add items to cache
+
+        for ( int i = start; i <= end; i++ )
+        {
+            jcs.put( i + ":key", region + " data " + i );
+        }
+
+        // Test that all items are in cache
+
+        for ( int i = start; i <= end; i++ )
+        {
+            String key = i + ":key";
+            String value = jcs.get( key );
+
+            assertEquals( "Wrong value for key [" + key + "]", region + " data " + i, value );
+        }
+
+        // Test that getElements returns all the expected values
+        Set<String> keys = new HashSet<>();
+        for ( int i = start; i <= end; i++ )
+        {
+            keys.add( i + ":key" );
+        }
+
+        Map<String, ICacheElement<String, String>> elements = jcs.getCacheElements( keys );
+        for ( int i = start; i <= end; i++ )
+        {
+            ICacheElement<String, String> element = elements.get( i + ":key" );
+            assertNotNull( "element " + i + ":key is missing", element );
+            assertEquals( "value " + i + ":key", region + " data " + i, element.getVal() );
+        }
+
+        // you can't remove in one thread and expect them to be in another //
+        //          Remove all the items
+        //
+        //          for ( int i = start; i <= end; i++ ) { jcs.remove( i + ":key" ); } //
+        //          Verify removal
+        //
+        //          for ( int i = start; i <= end; i++ ) { assertNull( "Removed key
+        //          should be null: " + i + ":key", jcs.get( i + ":key" ) ); }
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheSteadyLoadTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheSteadyLoadTest.java
new file mode 100644
index 0000000..ad700ac
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCacheSteadyLoadTest.java
@@ -0,0 +1,154 @@
+package org.apache.commons.jcs3.auxiliary.disk.indexed;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+import org.apache.commons.jcs3.auxiliary.disk.DiskTestObject;
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.utils.timing.ElapsedTimer;
+
+import java.text.DecimalFormat;
+import java.util.Random;
+
+/**
+ * This allows you to put thousands of large objects into the disk cache and to force removes to
+ * trigger optimizations along the way.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class IndexedDiskCacheSteadyLoadTest
+    extends TestCase
+{
+    /** For display */
+    private static final String LOG_DIVIDER = "---------------------------";
+
+    /** For getting memory info */
+    private static Runtime rt = Runtime.getRuntime();
+
+    /** For display */
+    private static DecimalFormat format = new DecimalFormat( "#,###" );
+
+    /**
+     * Insert 2000 wait 1 second, repeat. Average 1000 / sec.
+     * <p>
+     * @throws Exception
+     */
+    public void testRunSteadyLoadTest()
+        throws Exception
+    {
+        JCS.setConfigFilename( "/TestDiskCacheSteadyLoad.ccf" );
+
+        System.out.println( "runSteadyLoadTest" );
+
+        logMemoryUsage();
+
+        int numPerRun = 200;
+        long pauseBetweenRuns = 1000;
+        int runCount = 0;
+        int runs = 1000;
+        int upperKB = 50;
+
+        CacheAccess<String, DiskTestObject> jcs = JCS.getInstance( ( numPerRun / 2 ) + "aSecond" );
+
+        ElapsedTimer timer = new ElapsedTimer();
+        int numToGet = numPerRun * ( runs / 10 );
+        for ( int i = 0; i < numToGet; i++ )
+        {
+            jcs.get( String.valueOf( i ) );
+        }
+        System.out.println( LOG_DIVIDER );
+        System.out.println( "After getting " + numToGet );
+        System.out.println( "Elapsed " + timer.getElapsedTimeString() );
+        logMemoryUsage();
+
+        jcs.clear();
+        Thread.sleep( 3000 );
+        System.out.println( LOG_DIVIDER );
+        System.out.println( "Start putting" );
+
+        long totalSize = 0;
+        int totalPut = 0;
+
+        Random random = new Random( 89 );
+        while ( runCount < runs )
+        {
+            runCount++;
+            for ( int i = 0; i < numPerRun; i++ )
+            {
+                // 1/2 upper to upperKB-4 KB
+                int kiloBytes = Math.max( upperKB / 2, random.nextInt( upperKB ) );
+                int bytes = ( kiloBytes ) * 1024;
+                totalSize += bytes;
+                totalPut++;
+                DiskTestObject object = new DiskTestObject( Integer.valueOf( i ), new byte[bytes] );
+                jcs.put( String.valueOf( totalPut ), object );
+            }
+
+            // remove half of those inserted the previous run
+            if ( runCount > 1 )
+            {
+                for ( int j = ( ( totalPut - numPerRun ) - ( numPerRun / 2 ) ); j < ( totalPut - numPerRun ); j++ )
+                {
+                    jcs.remove( String.valueOf( j ) );
+                }
+            }
+
+            Thread.sleep( pauseBetweenRuns );
+            if ( runCount % 100 == 0 )
+            {
+                System.out.println( LOG_DIVIDER );
+                System.out.println( "Elapsed " + timer.getElapsedTimeString() );
+                System.out.println( "Run count: " + runCount + " Average size: " + ( totalSize / totalPut ) + "\n"
+                    + jcs.getStats() );
+                logMemoryUsage();
+            }
+        }
+
+        Thread.sleep( 3000 );
+        System.out.println( jcs.getStats() );
+        logMemoryUsage();
+
+        Thread.sleep( 10000 );
+        System.out.println( jcs.getStats() );
+        logMemoryUsage();
+
+        System.gc();
+        Thread.sleep( 3000 );
+        System.gc();
+        System.out.println( jcs.getStats() );
+        logMemoryUsage();
+    }
+
+    /**
+     * Logs the memory usage.
+     */
+    private static void logMemoryUsage()
+    {
+        long byte2MB = 1024 * 1024;
+        long total = rt.totalMemory() / byte2MB;
+        long free = rt.freeMemory() / byte2MB;
+        long used = total - free;
+        System.out.println( LOG_DIVIDER );
+        System.out.println( "Memory:" + " Used:" + format.format( used ) + "MB" + " Free:" + format.format( free )
+            + "MB" + " Total:" + format.format( total ) + "MB" );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/LRUMapSizeVsCount.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/LRUMapSizeVsCount.java
new file mode 100644
index 0000000..3bbf6d3
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/indexed/LRUMapSizeVsCount.java
@@ -0,0 +1,240 @@
+package org.apache.commons.jcs3.auxiliary.disk.indexed;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Map;
+
+import org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCache;
+import org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskElementDescriptor;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * This ensures that the jcs version of the LRU map is as fast as the commons
+ * version. It has been testing at .6 to .7 times the commons LRU.
+ * <p>
+ * @author aaronsm
+ *
+ */
+public class LRUMapSizeVsCount
+    extends TestCase
+{
+    /** The put put ration after the test */
+    double ratioPut = 0;
+
+    /** The ratio after the test */
+    double ratioGet = 0;
+
+    /** put size / count  ratio */
+    float targetPut = 1.2f;
+
+    /** get size / count ratio */
+    float targetGet = 1.2f;
+
+    /** Time to loop */
+    int loops = 20;
+
+    /** items to put and get per loop */
+    int tries = 100000;
+
+    /**
+     * @param testName
+     */
+    public LRUMapSizeVsCount( String testName )
+    {
+        super( testName );
+    }
+
+    /**
+     * A unit test suite for JUnit
+     * <p>
+     * @return The test suite
+     */
+    public static Test suite()
+    {
+        return new TestSuite( LRUMapSizeVsCount.class );
+    }
+
+    /**
+     * A unit test for JUnit
+     *
+     * @throws Exception
+     *                Description of the Exception
+     */
+    public void testSimpleLoad()
+        throws Exception
+    {
+        doWork();
+        assertTrue( this.ratioPut < targetPut );
+        assertTrue( this.ratioGet < targetGet );
+    }
+
+    /**
+     *
+     */
+    public void doWork()
+    {
+        long start = 0;
+        long end = 0;
+        long time = 0;
+        float tPer = 0;
+
+        long putTotalCount = 0;
+        long getTotalCount = 0;
+        long putTotalSize = 0;
+        long getTotalSize = 0;
+
+        long minTimeSizePut = Long.MAX_VALUE;
+        long minTimeSizeGet = Long.MAX_VALUE;
+        long minTimeCountPut = Long.MAX_VALUE;
+        long minTimeCountGet = Long.MAX_VALUE;
+
+        String cacheName = "LRUMap";
+        String cache2Name = "";
+
+        try
+        {
+        	IndexedDiskCacheAttributes cattr = new IndexedDiskCacheAttributes();
+        	cattr.setName("junit");
+        	cattr.setCacheName("junit");
+        	cattr.setDiskPath(".");
+        	IndexedDiskCache<String, String> idc = new IndexedDiskCache<>(cattr);
+
+			Map<String, IndexedDiskElementDescriptor> cacheCount = idc.new LRUMapCountLimited( tries );
+			Map<String, IndexedDiskElementDescriptor> cacheSize = idc.new LRUMapSizeLimited( tries/1024/2 );
+
+            for ( int j = 0; j < loops; j++ )
+            {
+                cacheName = "LRU Count           ";
+                start = System.currentTimeMillis();
+                for ( int i = 0; i < tries; i++ )
+                {
+                    cacheCount.put( "key:" + i,  new IndexedDiskElementDescriptor(i, i) );
+                }
+                end = System.currentTimeMillis();
+                time = end - start;
+                putTotalCount += time;
+                minTimeCountPut = Math.min(time, minTimeCountPut);
+                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
+                System.out.println( cacheName + " put time for " + tries + " = " + time + "; millis per = " + tPer );
+
+                start = System.currentTimeMillis();
+                for ( int i = 0; i < tries; i++ )
+                {
+                    cacheCount.get( "key:" + i );
+                }
+                end = System.currentTimeMillis();
+                time = end - start;
+                getTotalCount += time;
+                minTimeCountGet = Math.min(minTimeCountGet, time);
+                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
+                System.out.println( cacheName + " get time for " + tries + " = " + time + "; millis per = " + tPer );
+
+                ///////////////////////////////////////////////////////////////
+                cache2Name = "LRU Size            ";
+                //or LRUMapJCS
+                //cache2Name = "Hashtable";
+                //Hashtable cache2 = new Hashtable();
+                start = System.currentTimeMillis();
+                for ( int i = 0; i < tries; i++ )
+                {
+                    cacheSize.put( "key:" + i, new IndexedDiskElementDescriptor(i, i) );
+                }
+                end = System.currentTimeMillis();
+                time = end - start;
+                putTotalSize += time;
+                minTimeSizePut = Math.min(minTimeSizePut, time);
+
+                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
+                System.out.println( cache2Name + " put time for " + tries + " = " + time + "; millis per = " + tPer );
+
+                start = System.currentTimeMillis();
+                for ( int i = 0; i < tries; i++ )
+                {
+                    cacheSize.get( "key:" + i );
+                }
+                end = System.currentTimeMillis();
+                time = end - start;
+                getTotalSize += time;
+                minTimeSizeGet = Math.min(minTimeSizeGet, time);
+
+                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
+                System.out.println( cache2Name + " get time for " + tries + " = " + time + "; millis per = " + tPer );
+
+                System.out.println( "\n" );
+            }
+        }
+        catch ( Exception e )
+        {
+            e.printStackTrace( System.out );
+            System.out.println( e );
+        }
+
+        long putAvCount = putTotalCount / loops;
+        long getAvCount = getTotalCount / loops;
+        long putAvSize = putTotalSize / loops;
+        long getAvSize = getTotalSize / loops;
+
+        System.out.println( "Finished " + loops + " loops of " + tries + " gets and puts" );
+
+        System.out.println( "\n" );
+        System.out.println( "Put average for " + cacheName +  " = " + putAvCount );
+        System.out.println( "Put average for " + cache2Name + " = " + putAvSize );
+        ratioPut = (putAvSize *1.0) / putAvCount;
+        System.out.println( cache2Name.trim() + " puts took " + ratioPut + " times the " + cacheName.trim() + ", the goal is <" + targetPut
+            + "x" );
+
+        System.out.println( "\n" );
+        System.out.println( "Put minimum for " + cacheName +  " = " + minTimeCountPut );
+        System.out.println( "Put minimum for " + cache2Name + " = " + minTimeSizePut );
+        ratioPut = (minTimeSizePut * 1.0) / minTimeCountPut;
+        System.out.println( cache2Name.trim() + " puts took " + ratioPut + " times the " + cacheName.trim() + ", the goal is <" + targetPut
+            + "x" );
+
+        System.out.println( "\n" );
+        System.out.println( "Get average for " + cacheName + " = " + getAvCount );
+        System.out.println( "Get average for " + cache2Name + " = " + getAvSize );
+        ratioGet = Float.intBitsToFloat( (int) getAvCount ) / Float.intBitsToFloat( (int) getAvSize );
+        ratioGet = (getAvSize * 1.0) / getAvCount;
+        System.out.println( cache2Name.trim() + " gets took " + ratioGet + " times the " + cacheName.trim() + ", the goal is <" + targetGet
+            + "x" );
+
+        System.out.println( "\n" );
+        System.out.println( "Get minimum for " + cacheName +  " = " + minTimeCountGet );
+        System.out.println( "Get minimum for " + cache2Name + " = " + minTimeSizeGet );
+        ratioPut = (minTimeSizeGet * 1.0) / minTimeCountGet;
+        System.out.println( cache2Name.trim() + " puts took " + ratioPut + " times the " + cacheName.trim() + ", the goal is <" + targetGet
+            + "x" );
+
+    }
+
+    /**
+     * @param args
+     */
+    public static void main( String args[] )
+    {
+    	LRUMapSizeVsCount test = new LRUMapSizeVsCount( "command" );
+        test.doWork();
+    }
+
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/HsqlSetupTableUtil.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/HsqlSetupTableUtil.java
new file mode 100644
index 0000000..7246109
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/HsqlSetupTableUtil.java
@@ -0,0 +1,43 @@
+package org.apache.commons.jcs3.auxiliary.disk.jdbc;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.hsql.HSQLDiskCacheFactory;
+
+/** Can use this to setup a table. */
+public class HsqlSetupTableUtil extends HSQLDiskCacheFactory
+{
+    /**
+     * SETUP a TABLE FOR CACHE testing
+     * <p>
+     * @param cConn
+     * @param tableName
+     *
+     * @throws SQLException if database problems occur
+     */
+    public static void setupTABLE( Connection cConn, String tableName ) throws SQLException
+    {
+        HsqlSetupTableUtil util = new HsqlSetupTableUtil();
+        util.setupTable(cConn, tableName);
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/JDBCDataSourceFactoryUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/JDBCDataSourceFactoryUnitTest.java
new file mode 100644
index 0000000..bbec16b
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/JDBCDataSourceFactoryUnitTest.java
@@ -0,0 +1,207 @@
+package org.apache.commons.jcs3.auxiliary.disk.jdbc;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.naming.spi.InitialContextFactory;
+
+import org.apache.commons.dbcp2.BasicDataSource;
+import org.apache.commons.dbcp2.datasources.SharedPoolDataSource;
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.JDBCDiskCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.JDBCDiskCacheFactory;
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.dsfactory.DataSourceFactory;
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.dsfactory.JndiDataSourceFactory;
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.dsfactory.SharedPoolDataSourceFactory;
+
+import junit.framework.TestCase;
+
+/** Unit tests for the data source factories */
+public class JDBCDataSourceFactoryUnitTest
+    extends TestCase
+{
+    /** Verify that we can configure the object based on the props.
+     *  @throws SQLException
+     */
+    public void testConfigureDataSourceFactory_Simple() throws SQLException
+    {
+        // SETUP
+        String poolName = "testConfigurePoolAccessAttributes_Simple";
+
+        String url = "adfads";
+        String userName = "zvzvz";
+        String password = "qewrrewq";
+        int maxActive = 10;
+        String driverClassName = "org.hsqldb.jdbcDriver";
+
+        Properties props = new Properties();
+        String prefix = JDBCDiskCacheFactory.POOL_CONFIGURATION_PREFIX
+    		+ poolName
+            + JDBCDiskCacheFactory.ATTRIBUTE_PREFIX;
+        props.put( prefix + ".url", url );
+        props.put( prefix + ".userName", userName );
+        props.put( prefix + ".password", password );
+        props.put( prefix + ".maxActive", String.valueOf( maxActive ) );
+        props.put( prefix + ".driverClassName", driverClassName );
+
+        JDBCDiskCacheFactory factory = new JDBCDiskCacheFactory();
+        factory.initialize();
+
+        JDBCDiskCacheAttributes cattr = new JDBCDiskCacheAttributes();
+        cattr.setConnectionPoolName( poolName );
+
+        // DO WORK
+        DataSourceFactory result = factory.getDataSourceFactory( cattr, props );
+        assertTrue("Should be a shared pool data source factory", result instanceof SharedPoolDataSourceFactory);
+
+        SharedPoolDataSource spds = (SharedPoolDataSource) result.getDataSource();
+        assertNotNull( "Should have a data source class", spds );
+
+        // VERIFY
+        assertEquals( "Wrong pool name", poolName, spds.getDescription() );
+        assertEquals( "Wrong maxActive value", maxActive, spds.getMaxTotal() );
+    }
+
+    /** Verify that we can configure the object based on the attributes.
+     *  @throws SQLException
+     */
+    public void testConfigureDataSourceFactory_Attributes() throws SQLException
+    {
+        // SETUP
+        String url = "adfads";
+        String userName = "zvzvz";
+        String password = "qewrrewq";
+        int maxActive = 10;
+        String driverClassName = "org.hsqldb.jdbcDriver";
+
+        JDBCDiskCacheFactory factory = new JDBCDiskCacheFactory();
+        factory.initialize();
+
+        JDBCDiskCacheAttributes cattr = new JDBCDiskCacheAttributes();
+        cattr.setUrl(url);
+        cattr.setUserName(userName);
+        cattr.setPassword(password);
+        cattr.setMaxTotal(maxActive);
+        cattr.setDriverClassName(driverClassName);
+
+        // DO WORK
+        DataSourceFactory result = factory.getDataSourceFactory( cattr, null );
+        assertTrue("Should be a shared pool data source factory", result instanceof SharedPoolDataSourceFactory);
+
+        SharedPoolDataSource spds = (SharedPoolDataSource) result.getDataSource();
+        assertNotNull( "Should have a data source class", spds );
+
+        // VERIFY
+        assertEquals( "Wrong maxActive value", maxActive, spds.getMaxTotal() );
+    }
+
+    /** Verify that we can configure the object based on JNDI.
+     *  @throws SQLException
+     */
+    public void testConfigureDataSourceFactory_JNDI() throws SQLException
+    {
+        // SETUP
+        String jndiPath = "java:comp/env/jdbc/MyDB";
+        long ttl = 300000L;
+
+        System.setProperty(Context.INITIAL_CONTEXT_FACTORY,
+                MockInitialContextFactory.class.getName());
+
+        MockInitialContextFactory.bind(jndiPath, new BasicDataSource());
+
+        JDBCDiskCacheFactory factory = new JDBCDiskCacheFactory();
+        factory.initialize();
+
+        JDBCDiskCacheAttributes cattr = new JDBCDiskCacheAttributes();
+        cattr.setJndiPath(jndiPath);
+        cattr.setJndiTTL(ttl);
+
+        // DO WORK
+        DataSourceFactory result = factory.getDataSourceFactory( cattr, null );
+        assertTrue("Should be a JNDI data source factory", result instanceof JndiDataSourceFactory);
+    }
+
+    /* For JNDI mocking */
+    public static class MockInitialContextFactory implements InitialContextFactory
+    {
+        private static Context context;
+
+        static
+        {
+            try
+            {
+                context = new InitialContext(true)
+                {
+                    Map<String, Object> bindings = new HashMap<>();
+
+                    @Override
+                    public void bind(String name, Object obj) throws NamingException
+                    {
+                        bindings.put(name, obj);
+                    }
+
+                    @Override
+                    public Object lookup(String name) throws NamingException
+                    {
+                        return bindings.get(name);
+                    }
+
+                    @Override
+                    public Hashtable<?, ?> getEnvironment() throws NamingException
+                    {
+                        return new Hashtable<>();
+                    }
+                };
+            }
+            catch (NamingException e)
+            {
+            	// can't happen.
+                throw new RuntimeException(e);
+            }
+        }
+
+        @Override
+		public Context getInitialContext(Hashtable<?, ?> environment) throws NamingException
+        {
+            return context;
+        }
+
+        public static void bind(String name, Object obj)
+        {
+            try
+            {
+                context.bind(name, obj);
+            }
+            catch (NamingException e)
+            {
+            	// can't happen.
+                throw new RuntimeException(e);
+            }
+        }
+    }
+}
+
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/JDBCDiskCacheRemovalUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/JDBCDiskCacheRemovalUnitTest.java
new file mode 100644
index 0000000..c103a81
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/JDBCDiskCacheRemovalUnitTest.java
@@ -0,0 +1,109 @@
+package org.apache.commons.jcs3.auxiliary.disk.jdbc;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.Properties;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+
+import junit.framework.TestCase;
+
+/** Tests for the removal functionality. */
+public class JDBCDiskCacheRemovalUnitTest
+    extends TestCase
+{
+    /** db name -- set in system props */
+    private final String databaseName = "JCS_STORE_REMOVAL";
+
+    /**
+     * Test setup
+     */
+    @Override
+    public void setUp()
+    {
+        System.setProperty( "DATABASE_NAME", databaseName );
+        JCS.setConfigFilename( "/TestJDBCDiskCacheRemoval.ccf" );
+    }
+
+    /**
+     * Verify the fix for BUG JCS-20
+     * <p>
+     * Setup an hsql db. Add an item. Remove using partial key.
+     * @throws Exception
+     */
+    public void testPartialKeyRemoval_Good()
+        throws Exception
+    {
+        // SETUP
+        setupDatabase();
+
+        String keyPart1 = "part1";
+        String keyPart2 = "part2";
+        String region = "testCache1";
+        String data = "adfadsfasfddsafasasd";
+
+        CacheAccess<String, String> jcs = JCS.getInstance( region );
+
+        // DO WORK
+        jcs.put( keyPart1 + ":" + keyPart2, data );
+        Thread.sleep( 1000 );
+
+        // VERIFY
+        String resultBeforeRemove = jcs.get( keyPart1 + ":" + keyPart2 );
+        assertEquals( "Wrong result", data, resultBeforeRemove );
+
+        jcs.remove( keyPart1 + ":" );
+        String resultAfterRemove = jcs.get( keyPart1 + ":" + keyPart2 );
+        assertNull( "Should not have a result after removal.", resultAfterRemove );
+
+//        System.out.println( jcs.getStats() );
+    }
+
+    /**
+     * Create the database.
+     * @throws InstantiationException
+     * @throws IllegalAccessException
+     * @throws ClassNotFoundException
+     * @throws SQLException
+     */
+    private void setupDatabase()
+        throws InstantiationException, IllegalAccessException, ClassNotFoundException, SQLException
+    {
+        System.setProperty( "hsqldb.cache_scale", "8" );
+
+        String rafroot = "target";
+        Properties p = new Properties();
+        String driver = p.getProperty( "driver", "org.hsqldb.jdbcDriver" );
+        String url = p.getProperty( "url", "jdbc:hsqldb:" );
+        String database = p.getProperty( "database", rafroot + "/JDBCDiskCacheRemovalUnitTest" );
+        String user = p.getProperty( "user", "sa" );
+        String password = p.getProperty( "password", "" );
+
+        new org.hsqldb.jdbcDriver();
+        Class.forName( driver ).newInstance();
+        Connection cConn = DriverManager.getConnection( url + database, user, password );
+
+        HsqlSetupTableUtil.setupTABLE( cConn, databaseName );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/JDBCDiskCacheSharedPoolUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/JDBCDiskCacheSharedPoolUnitTest.java
new file mode 100644
index 0000000..7a2d233
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/JDBCDiskCacheSharedPoolUnitTest.java
@@ -0,0 +1,142 @@
+package org.apache.commons.jcs3.auxiliary.disk.jdbc;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+
+/**
+ * Runs basic tests for the JDBC disk cache using a shared connection pool.
+ *<p>
+ * @author Aaron Smuts
+ */
+public class JDBCDiskCacheSharedPoolUnitTest
+    extends TestCase
+{
+    /** Test setup */
+    @Override
+    public void setUp()
+    {
+        JCS.setConfigFilename( "/TestJDBCDiskCacheSharedPool.ccf" );
+    }
+
+    /**
+     * Test the basic JDBC disk cache functionality with a hsql backing.
+     * @throws Exception
+     */
+    public void testSimpleJDBCPutGetWithHSQL()
+        throws Exception
+    {
+        System.setProperty( "hsqldb.cache_scale", "8" );
+
+        String rafroot = "target";
+        Properties p = new Properties();
+        String driver = p.getProperty( "driver", "org.hsqldb.jdbcDriver" );
+        String url = p.getProperty( "url", "jdbc:hsqldb:" );
+        String database = p.getProperty( "database", rafroot + "/cache_hsql_db_sharedpool" );
+        String user = p.getProperty( "user", "sa" );
+        String password = p.getProperty( "password", "" );
+
+        new org.hsqldb.jdbcDriver();
+        Class.forName( driver ).newInstance();
+        Connection cConn = DriverManager.getConnection( url + database, user, password );
+
+        HsqlSetupTableUtil.setupTABLE( cConn, "JCS_STORE_0" );
+
+        HsqlSetupTableUtil.setupTABLE( cConn, "JCS_STORE_1" );
+
+        runTestForRegion( "testCache1", 200 );
+    }
+
+    /**
+     * Adds items to cache, gets them, and removes them. The item count is more than the size of the
+     * memory cache, so items should spool to disk.
+     * <p>
+     * @param region Name of the region to access
+     * @param items
+     * @throws Exception If an error occurs
+     */
+    public void runTestForRegion( String region, int items )
+        throws Exception
+    {
+        CacheAccess<String, String> jcs = JCS.getInstance( region );
+
+//        System.out.println( "BEFORE PUT \n" + jcs.getStats() );
+
+        // Add items to cache
+
+        for ( int i = 0; i <= items; i++ )
+        {
+            jcs.put( i + ":key", region + " data " + i );
+        }
+
+//        System.out.println( jcs.getStats() );
+
+        Thread.sleep( 1000 );
+
+//        System.out.println( jcs.getStats() );
+
+        // Test that all items are in cache
+
+        for ( int i = 0; i <= items; i++ )
+        {
+            String value = jcs.get( i + ":key" );
+
+            assertEquals( "key = [" + i + ":key] value = [" + value + "]", region + " data " + i, value );
+        }
+
+        // Test that getElements returns all the expected values
+        Set<String> keys = new HashSet<>();
+        for ( int i = 0; i <= items; i++ )
+        {
+            keys.add( i + ":key" );
+        }
+
+        Map<String, ICacheElement<String, String>> elements = jcs.getCacheElements( keys );
+        for ( int i = 0; i <= items; i++ )
+        {
+            ICacheElement<String, String> element = elements.get( i + ":key" );
+            assertNotNull( "element " + i + ":key is missing", element );
+            assertEquals( "value " + i + ":key", region + " data " + i, element.getVal() );
+        }
+
+        // Remove all the items
+        for ( int i = 0; i <= items; i++ )
+        {
+            jcs.remove( i + ":key" );
+        }
+
+        // Verify removal
+        for ( int i = 0; i <= items; i++ )
+        {
+            assertNull( "Removed key should be null: " + i + ":key", jcs.get( i + ":key" ) );
+        }
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/JDBCDiskCacheShrinkUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/JDBCDiskCacheShrinkUnitTest.java
new file mode 100644
index 0000000..264be54
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/JDBCDiskCacheShrinkUnitTest.java
@@ -0,0 +1,222 @@
+package org.apache.commons.jcs3.auxiliary.disk.jdbc;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.util.Properties;
+
+import org.apache.commons.jcs3.utils.timing.SleepUtil;
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.access.exception.CacheException;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Runs basic tests for the JDBC disk cache.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class JDBCDiskCacheShrinkUnitTest
+{
+    /**
+     * Creates the DB
+     * <p>
+     * @throws Exception
+     */
+    @BeforeClass
+    public static void setupDatabase() throws Exception
+    {
+        System.setProperty( "hsqldb.cache_scale", "8" );
+
+        String rafroot = "target";
+        Properties p = new Properties();
+        String driver = p.getProperty( "driver", "org.hsqldb.jdbcDriver" );
+        String url = p.getProperty( "url", "jdbc:hsqldb:" );
+        String database = p.getProperty( "database", rafroot + "/JDBCDiskCacheShrinkUnitTest" );
+        String user = p.getProperty( "user", "sa" );
+        String password = p.getProperty( "password", "" );
+
+        new org.hsqldb.jdbcDriver();
+        Class.forName( driver ).newInstance();
+        Connection cConn = DriverManager.getConnection( url + database, user, password );
+
+        HsqlSetupTableUtil.setupTABLE( cConn, "JCS_STORE_SHRINK" );
+    }
+
+    /**
+     * Test setup
+     */
+    @Before
+    public void setUp()
+    {
+        JCS.setConfigFilename( "/TestJDBCDiskCacheShrink.ccf" );
+    }
+
+    /**
+     * Test the basic JDBC disk cache functionality with a hsql backing. Verify that items
+     * configured to expire after 1 second actually expire.
+     * <p>
+     * @throws Exception
+     */
+    @Test
+    public void testExpireInBackground()
+        throws Exception
+    {
+        String regionExpire = "expire1Second";
+        int items = 200;
+
+        CacheAccess<String, String> jcsExpire = JCS.getInstance( regionExpire );
+
+//        System.out.println( "BEFORE PUT \n" + jcsExpire.getStats() );
+
+        // Add items to cache
+
+        for ( int i = 0; i <= items; i++ )
+        {
+            jcsExpire.put( i + ":key", regionExpire + " data " + i );
+        }
+
+//        System.out.println( jcsExpire.getStats() );
+
+        // the shrinker is supposed to run every second
+        SleepUtil.sleepAtLeast( 3000 );
+
+//        System.out.println( jcsExpire.getStats() );
+
+        // Test that all items have been removed from the cache
+        for ( int i = 0; i <= items; i++ )
+        {
+            assertNull( "Removed key should be null: " + i + ":key", jcsExpire.get( i + ":key" ) );
+        }
+    }
+
+    /**
+     * Verify that those not scheduled to expire do not expire.
+     * <p>
+     * @throws CacheException
+     * @throws InterruptedException
+     */
+    @Test
+    public void testDidNotExpire()
+        throws CacheException, InterruptedException
+    {
+        String region = "expire100Second";
+        int items = 200;
+
+        CacheAccess<String, String> jcs = JCS.getInstance( region );
+
+//        System.out.println( "BEFORE PUT \n" + jcs.getStats() );
+
+        // Add items to cache
+
+        for ( int i = 0; i <= items; i++ )
+        {
+            jcs.put( i + ":key", region + " data " + i );
+        }
+
+//        System.out.println( jcs.getStats() );
+
+        SleepUtil.sleepAtLeast( 1000 );
+
+//        System.out.println( jcs.getStats() );
+
+        // Test that all items are in cache
+
+        for ( int i = 0; i <= items; i++ )
+        {
+            String value = jcs.get( i + ":key" );
+
+            assertEquals( "key = [" + i + ":key] value = [" + value + "]", region + " data " + i, value );
+        }
+
+        // Remove all the items
+
+        for ( int i = 0; i <= items; i++ )
+        {
+            jcs.remove( i + ":key" );
+        }
+
+        // Verify removal
+
+        for ( int i = 0; i <= items; i++ )
+        {
+            assertNull( "Removed key should be null: " + i + ":key", jcs.get( i + ":key" ) );
+        }
+    }
+
+    /**
+     * Verify that eternal trumps max life.
+     * @throws CacheException
+     * @throws InterruptedException
+     */
+    @Test
+    public void testDidNotExpireEternal()
+        throws CacheException, InterruptedException
+    {
+        String region = "eternal";
+        int items = 200;
+
+        CacheAccess<String, String> jcs = JCS.getInstance( region );
+
+//        System.out.println( "BEFORE PUT \n" + jcs.getStats() );
+
+        // Add items to cache
+
+        for ( int i = 0; i <= items; i++ )
+        {
+            jcs.put( i + ":key", region + " data " + i );
+        }
+
+//        System.out.println( jcs.getStats() );
+
+        SleepUtil.sleepAtLeast( 1000 );
+
+//        System.out.println( jcs.getStats() );
+
+        // Test that all items are in cache
+
+        for ( int i = 0; i <= items; i++ )
+        {
+            String value = jcs.get( i + ":key" );
+
+            assertEquals( "key = [" + i + ":key] value = [" + value + "]", region + " data " + i, value );
+        }
+
+        // Remove all the items
+
+        for ( int i = 0; i <= items; i++ )
+        {
+            jcs.remove( i + ":key" );
+        }
+
+        // Verify removal
+
+        for ( int i = 0; i <= items; i++ )
+        {
+            assertNull( "Removed key should be null: " + i + ":key", jcs.get( i + ":key" ) );
+        }
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/JDBCDiskCacheUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/JDBCDiskCacheUnitTest.java
new file mode 100644
index 0000000..a58423b
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/JDBCDiskCacheUnitTest.java
@@ -0,0 +1,209 @@
+package org.apache.commons.jcs3.auxiliary.disk.jdbc;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.Executors;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.jcs3.engine.control.MockCompositeCacheManager;
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.JDBCDiskCache;
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.JDBCDiskCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.JDBCDiskCacheFactory;
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.dsfactory.DataSourceFactory;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.utils.serialization.StandardSerializer;
+import org.apache.commons.jcs3.utils.threadpool.DaemonThreadFactory;
+
+/**
+ * Runs basic tests for the JDBC disk cache.
+ *<p>
+ * @author Aaron Smuts
+ */
+public class JDBCDiskCacheUnitTest
+    extends TestCase
+{
+    /** Test setup */
+    @Override
+    public void setUp()
+    {
+        JCS.setConfigFilename( "/TestJDBCDiskCache.ccf" );
+    }
+
+    /**
+     * Test the basic JDBC disk cache functionality with a hsql backing.
+     * @throws Exception
+     */
+    public void testSimpleJDBCPutGetWithHSQL()
+        throws Exception
+    {
+        System.setProperty( "hsqldb.cache_scale", "8" );
+
+        String rafroot = "target";
+        Properties p = new Properties();
+        String driver = p.getProperty( "driver", "org.hsqldb.jdbcDriver" );
+        String url = p.getProperty( "url", "jdbc:hsqldb:" );
+        String database = p.getProperty( "database", rafroot + "/cache_hsql_db" );
+        String user = p.getProperty( "user", "sa" );
+        String password = p.getProperty( "password", "" );
+
+        new org.hsqldb.jdbcDriver();
+        Class.forName( driver ).newInstance();
+        Connection cConn = DriverManager.getConnection( url + database, user, password );
+
+        HsqlSetupTableUtil.setupTABLE( cConn, "JCS_STORE2" );
+
+        runTestForRegion( "testCache1", 200 );
+    }
+
+    /**
+     * Adds items to cache, gets them, and removes them. The item count is more than the size of the
+     * memory cache, so items should spool to disk.
+     * <p>
+     * @param region Name of the region to access
+     * @param items
+     * @throws Exception If an error occurs
+     */
+    public void runTestForRegion( String region, int items )
+        throws Exception
+    {
+        CacheAccess<String, String> jcs = JCS.getInstance( region );
+
+//        System.out.println( "BEFORE PUT \n" + jcs.getStats() );
+
+        // Add items to cache
+
+        for ( int i = 0; i <= items; i++ )
+        {
+            jcs.put( i + ":key", region + " data " + i );
+        }
+
+//        System.out.println( jcs.getStats() );
+
+        Thread.sleep( 1000 );
+
+//        System.out.println( jcs.getStats() );
+
+        // Test that all items are in cache
+
+        for ( int i = 0; i <= items; i++ )
+        {
+            String value = jcs.get( i + ":key" );
+
+            assertEquals( "key = [" + i + ":key] value = [" + value + "]", region + " data " + i, value );
+        }
+
+        // Test that getElements returns all the expected values
+        Set<String> keys = new HashSet<>();
+        for ( int i = 0; i <= items; i++ )
+        {
+            keys.add( i + ":key" );
+        }
+
+        Map<String, ICacheElement<String, String>> elements = jcs.getCacheElements( keys );
+        for ( int i = 0; i <= items; i++ )
+        {
+            ICacheElement<String, String> element = elements.get( i + ":key" );
+            assertNotNull( "element " + i + ":key is missing", element );
+            assertEquals( "value " + i + ":key", region + " data " + i, element.getVal() );
+        }
+
+        // Remove all the items
+        for ( int i = 0; i <= items; i++ )
+        {
+            jcs.remove( i + ":key" );
+        }
+
+        // Verify removal
+        for ( int i = 0; i <= items; i++ )
+        {
+            assertNull( "Removed key should be null: " + i + ":key", jcs.get( i + ":key" ) );
+        }
+    }
+
+    /**
+     * Verfiy that it uses the pool access manager config.
+     * <p>
+     * @throws Exception
+     */
+    public void testInitializePoolAccess_withPoolName()
+        throws Exception
+    {
+        // SETUP
+        String poolName = "testInitializePoolAccess_withPoolName";
+
+        String url = "jdbc:hsqldb:";
+        String userName = "sa";
+        String password = "";
+        int maxActive = 10;
+        String driverClassName = "org.hsqldb.jdbcDriver";
+
+        Properties props = new Properties();
+        String prefix = JDBCDiskCacheFactory.POOL_CONFIGURATION_PREFIX
+    		+ poolName
+            + JDBCDiskCacheFactory.ATTRIBUTE_PREFIX;
+        props.put( prefix + ".url", url );
+        props.put( prefix + ".userName", userName );
+        props.put( prefix + ".password", password );
+        props.put( prefix + ".maxActive", String.valueOf( maxActive ) );
+        props.put( prefix + ".driverClassName", driverClassName );
+
+        JDBCDiskCacheAttributes cattr = new JDBCDiskCacheAttributes();
+        cattr.setConnectionPoolName( poolName );
+        cattr.setTableName("JCSTESTTABLE_InitializePoolAccess");
+
+        MockCompositeCacheManager compositeCacheManager = new MockCompositeCacheManager();
+        compositeCacheManager.setConfigurationProperties( props );
+        JDBCDiskCacheFactory dcFactory = new JDBCDiskCacheFactory();
+        dcFactory.initialize();
+        dcFactory.setScheduledExecutorService(Executors.newScheduledThreadPool(2,
+        	new DaemonThreadFactory("JCS-JDBCDiskCacheManager-", Thread.MIN_PRIORITY)));
+
+        JDBCDiskCache<String, String> diskCache = dcFactory.createCache( cattr, compositeCacheManager, null, new StandardSerializer() );
+        assertNotNull( "Should have a cache instance", diskCache );
+
+        // DO WORK
+        DataSourceFactory result = dcFactory.getDataSourceFactory(cattr, props);
+
+        // VERIFY
+        assertNotNull( "Should have a data source factory class", result );
+        assertEquals( "wrong name", poolName, result.getName() );
+
+        System.setProperty( "hsqldb.cache_scale", "8" );
+
+        String rafroot = "target";
+        String database = rafroot + "/cache_hsql_db";
+
+        new org.hsqldb.jdbcDriver();
+        Class.forName( driverClassName ).newInstance();
+        Connection cConn = DriverManager.getConnection( url + database, userName, password );
+
+        HsqlSetupTableUtil.setupTABLE( cConn, "JCSTESTTABLE_InitializePoolAccess" );
+
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/hsql/HSQLDiskCacheConcurrentUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/hsql/HSQLDiskCacheConcurrentUnitTest.java
new file mode 100644
index 0000000..c85a6e6
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/hsql/HSQLDiskCacheConcurrentUnitTest.java
@@ -0,0 +1,161 @@
+package org.apache.commons.jcs3.auxiliary.disk.jdbc.hsql;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.extensions.ActiveTestSuite;
+import junit.framework.Test;
+import junit.framework.TestCase;
+
+/**
+ * Test which exercises the indexed disk cache. This one uses three different regions for thre
+ * threads.
+ */
+public class HSQLDiskCacheConcurrentUnitTest
+    extends TestCase
+{
+    /**
+     * Number of items to cache, twice the configured maxObjects for the memory cache regions.
+     */
+    private static int items = 100;
+
+    /**
+     * Constructor for the TestDiskCache object.
+     * @param testName
+     */
+    public HSQLDiskCacheConcurrentUnitTest( String testName )
+    {
+        super( testName );
+    }
+
+    /**
+     * A unit test suite for JUnit. Uses ActiveTestSuite to run multiple tests concurrently.
+     * <p>
+     * @return The test suite
+     */
+    public static Test suite()
+    {
+        ActiveTestSuite suite = new ActiveTestSuite();
+
+        suite.addTest( new HSQLDiskCacheConcurrentUnitTest( "testHSQLDiskCache1" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "indexedRegion1" );
+            }
+        } );
+
+        suite.addTest( new HSQLDiskCacheConcurrentUnitTest( "testHSQLDiskCache2" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "indexedRegion2" );
+            }
+        } );
+
+        suite.addTest( new HSQLDiskCacheConcurrentUnitTest( "testHSQLDiskCache3" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "indexedRegion3" );
+            }
+        } );
+
+        return suite;
+    }
+
+    /**
+     * Test setup
+     */
+    @Override
+    public void setUp()
+    {
+        JCS.setConfigFilename( "/TestHSQLDiskCacheConcurrent.ccf" );
+    }
+
+    /**
+     * Adds items to cache, gets them, and removes them. The item count is more than the size of the
+     * memory cache, so items should spool to disk.
+     * <p>
+     * @param region Name of the region to access
+     * @throws Exception If an error occurs
+     */
+    public void runTestForRegion( String region )
+        throws Exception
+    {
+        CacheAccess<String, String> jcs = JCS.getInstance( region );
+
+        // Add items to cache
+        for ( int i = 0; i <= items; i++ )
+        {
+            jcs.put( i + ":key", region + " data " + i );
+        }
+
+//        System.out.println( jcs.getStats() );
+
+        // Test that all items are in cache
+        for ( int i = 0; i <= items; i++ )
+        {
+            String value = jcs.get( i + ":key" );
+
+            assertEquals( "key = [" + i + ":key] value = [" + value + "]", region + " data " + i, value );
+        }
+
+        // Test that getElements returns all the expected values
+        Set<String> keys = new HashSet<>();
+        for ( int i = 0; i <= items; i++ )
+        {
+            keys.add( i + ":key" );
+        }
+
+        Map<String, ICacheElement<String, String>> elements = jcs.getCacheElements( keys );
+        for ( int i = 0; i <= items; i++ )
+        {
+            ICacheElement<String, String> element = elements.get( i + ":key" );
+            assertNotNull( "element " + i + ":key is missing", element );
+            assertEquals( "value " + i + ":key", region + " data " + i, element.getVal() );
+        }
+
+        // Remove all the items
+        for ( int i = 0; i <= items; i++ )
+        {
+            jcs.remove( i + ":key" );
+        }
+
+        // Verify removal
+        for ( int i = 0; i <= items; i++ )
+        {
+            assertNull( "Removed key should be null: " + i + ":key", jcs.get( i + ":key" ) );
+        }
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/hsql/HSQLDiskCacheUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/hsql/HSQLDiskCacheUnitTest.java
new file mode 100644
index 0000000..cf5132d
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/hsql/HSQLDiskCacheUnitTest.java
@@ -0,0 +1,173 @@
+package org.apache.commons.jcs3.auxiliary.disk.jdbc.hsql;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.access.exception.CacheException;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/**
+ * Test which exercises the HSQL cache.
+ */
+public class HSQLDiskCacheUnitTest
+    extends TestCase
+{
+    /**
+     * Test setup
+     */
+    @Override
+    public void setUp()
+    {
+        JCS.setConfigFilename( "/TestHSQLDiskCache.ccf" );
+    }
+
+    /**
+     * Adds items to cache, gets them, and removes them. The item count is more than the size of the
+     * memory cache, so items should spool to disk.
+     * <p>
+     * @throws Exception If an error occurs
+     */
+    public void testBasicPutRemove()
+        throws Exception
+    {
+        int items = 20;
+
+        String region = "testBasicPutRemove";
+
+        CacheAccess<String, String> jcs = JCS.getInstance( region );
+
+        // Add items to cache
+        for ( int i = 0; i <= items; i++ )
+        {
+            jcs.put( i + ":key", region + " data " + i );
+        }
+
+        // Test that all items are in cache
+        for ( int i = 0; i <= items; i++ )
+        {
+            String value = jcs.get( i + ":key" );
+            assertEquals( "key = [" + i + ":key] value = [" + value + "]", region + " data " + i, value );
+        }
+
+        // Test that getElements returns all the expected values
+        Set<String> keys = new HashSet<>();
+        for ( int i = 0; i <= items; i++ )
+        {
+            keys.add( i + ":key" );
+        }
+
+        Map<String, ICacheElement<String, String>> elements = jcs.getCacheElements( keys );
+        for ( int i = 0; i <= items; i++ )
+        {
+            ICacheElement<String, String> element = elements.get( i + ":key" );
+            assertNotNull( "element " + i + ":key is missing", element );
+            assertEquals( "value " + i + ":key", region + " data " + i, element.getVal() );
+        }
+
+        // Remove all the items
+        for ( int i = 0; i <= items; i++ )
+        {
+            jcs.remove( i + ":key" );
+        }
+
+        // Verify removal
+        for ( int i = 0; i <= items; i++ )
+        {
+            assertNull( "Removed key should be null: " + i + ":key", jcs.get( i + ":key" ) );
+        }
+    }
+
+    /**
+     * Verify that remove all work son a region where it is not prohibited.
+     * <p>
+     * @throws CacheException
+     * @throws InterruptedException
+     */
+    public void testRemoveAll()
+        throws CacheException, InterruptedException
+    {
+        String region = "removeAllAllowed";
+        CacheAccess<String, String> jcs = JCS.getInstance( region );
+
+        int items = 20;
+
+        // Add items to cache
+        for ( int i = 0; i <= items; i++ )
+        {
+            jcs.put( i + ":key", region + " data " + i );
+        }
+
+        // a db thread could be updating when we call remove all?
+        // there was a race on remove all, an element may be put to disk after it is called even
+        // though the put
+        // was called before clear.
+        // I discovered it and removed it.
+        // Thread.sleep( 500 );
+
+//        System.out.println( jcs.getStats() );
+
+        jcs.clear();
+
+        for ( int i = 0; i <= items; i++ )
+        {
+            String value = jcs.get( i + ":key" );
+            assertNull( "value should be null key = [" + i + ":key] value = [" + value + "]", value );
+        }
+    }
+
+    /**
+     * Verify that remove all does not work on a region where it is prohibited.
+     * <p>
+     * @throws CacheException
+     * @throws InterruptedException
+     */
+    public void testRemoveAllProhibition()
+        throws CacheException, InterruptedException
+    {
+        String region = "noRemoveAll";
+        CacheAccess<String, String> jcs = JCS.getInstance( region );
+
+        int items = 20;
+
+        // Add items to cache
+        for ( int i = 0; i <= items; i++ )
+        {
+            jcs.put( i + ":key", region + " data " + i );
+        }
+
+        // a db thread could be updating the disk when
+        // Thread.sleep( 500 );
+
+        jcs.clear();
+
+        for ( int i = 0; i <= items; i++ )
+        {
+            String value = jcs.get( i + ":key" );
+            assertEquals( "key = [" + i + ":key] value = [" + value + "]", region + " data " + i, value );
+        }
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/mysql/MySQLDiskCacheHsqlBackedUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/mysql/MySQLDiskCacheHsqlBackedUnitTest.java
new file mode 100644
index 0000000..d876b5d
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/mysql/MySQLDiskCacheHsqlBackedUnitTest.java
@@ -0,0 +1,178 @@
+package org.apache.commons.jcs3.auxiliary.disk.jdbc.mysql;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.HsqlSetupTableUtil;
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Runs basic tests for the JDBC disk cache.
+ * @author Aaron Smuts
+ */
+public class MySQLDiskCacheHsqlBackedUnitTest
+{
+    /**
+     * Creates the DB
+     * <p>
+     * @throws Exception
+     */
+    @BeforeClass
+    public static void setupDatabase() throws Exception
+    {
+        System.setProperty( "hsqldb.cache_scale", "8" );
+
+        String rafroot = "target";
+        String url = "jdbc:hsqldb:";
+        String database = rafroot + "/MySQLDiskCacheHsqlBackedUnitTest";
+        String user = "sa";
+        String password = "";
+
+        new org.hsqldb.jdbcDriver();
+        Connection cConn = DriverManager.getConnection( url + database, user, password );
+
+        HsqlSetupTableUtil.setupTABLE( cConn, "JCS_STORE_MYSQL" );
+    }
+
+    /**
+     * Test setup
+     */
+    @Before
+    public void setUp()
+    {
+        JCS.setConfigFilename( "/TestMySQLDiskCache.ccf" );
+    }
+
+    /**
+     * Test the basic JDBC disk cache functionality with a hsql backing.
+     * @throws Exception
+     */
+    @Test
+    public void testSimpleJDBCPutGetWithHSQL()
+        throws Exception
+    {
+        runTestForRegion( "testCache1", 200 );
+    }
+
+    /**
+     * Adds items to cache, gets them, and removes them. The item count is more than the size of the
+     * memory cache, so items should spool to disk.
+     * <p>
+     * @param region Name of the region to access
+     * @param items
+     * @throws Exception If an error occurs
+     */
+    public void runTestForRegion( String region, int items )
+        throws Exception
+    {
+        CacheAccess<String, String> jcs = JCS.getInstance( region );
+        //System.out.println( "BEFORE PUT \n" + jcs.getStats() );
+
+        // Add items to cache
+        for ( int i = 0; i < items; i++ )
+        {
+            jcs.put( i + ":key", region + " data " + i );
+        }
+
+        //System.out.println( jcs.getStats() );
+        Thread.sleep( 1000 );
+        //System.out.println( jcs.getStats() );
+
+        // Test that all items are in cache
+        for ( int i = 0; i < items; i++ )
+        {
+            String value = jcs.get( i + ":key" );
+
+            assertEquals( "key = [" + i + ":key] value = [" + value + "]", region + " data " + i, value );
+        }
+
+        // Test that getElements returns all the expected values
+        Set<String> keys = new HashSet<>();
+        for ( int i = 0; i < items; i++ )
+        {
+            keys.add( i + ":key" );
+        }
+
+        Map<String, ICacheElement<String, String>> elements = jcs.getCacheElements( keys );
+        for ( int i = 0; i < items; i++ )
+        {
+            ICacheElement<String, String> element = elements.get( i + ":key" );
+            assertNotNull( "element " + i + ":key is missing", element );
+            assertEquals( "value " + i + ":key", region + " data " + i, element.getVal() );
+        }
+
+        // Remove all the items
+        for ( int i = 0; i < items; i++ )
+        {
+            jcs.remove( i + ":key" );
+        }
+
+        // Verify removal
+
+        for ( int i = 0; i < items; i++ )
+        {
+            assertNull( "Removed key should be null: " + i + ":key", jcs.get( i + ":key" ) );
+        }
+    }
+
+    /**
+     * Test the basic JDBC disk cache functionality with a hsql backing.
+     * <p>
+     * @throws Exception
+     */
+    @Test
+    public void testPutGetMatchingWithHSQL()
+        throws Exception
+    {
+        // SETUP
+        int items = 200;
+        String region = "testCache2";
+        CacheAccess<String, String> jcs = JCS.getInstance( region );
+//        System.out.println( "BEFORE PUT \n" + jcs.getStats() );
+
+        // DO WORK
+        for ( int i = 0; i < items; i++ )
+        {
+            jcs.put( i + ":key", region + " data " + i );
+        }
+        Thread.sleep( 1000 );
+
+        Map<String, ICacheElement<String, String>> matchingResults = jcs.getMatchingCacheElements( "1.8.+" );
+
+        // VERIFY
+        assertEquals( "Wrong number returned", 10, matchingResults.size() );
+//        System.out.println( "matchingResults.keySet() " + matchingResults.keySet() );
+//        System.out.println( "\nAFTER TEST \n" + jcs.getStats() );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/mysql/MySQLDiskCacheUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/mysql/MySQLDiskCacheUnitTest.java
new file mode 100644
index 0000000..ee2cc64
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/mysql/MySQLDiskCacheUnitTest.java
@@ -0,0 +1,74 @@
+package org.apache.commons.jcs3.auxiliary.disk.jdbc.mysql;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.sql.SQLException;
+
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.TableState;
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.dsfactory.SharedPoolDataSourceFactory;
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.mysql.MySQLDiskCache;
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.mysql.MySQLDiskCacheAttributes;
+import org.apache.commons.jcs3.engine.control.CompositeCacheManager;
+
+import junit.framework.TestCase;
+
+/**
+ * Simple tests for the MySQLDisk Cache.
+ * <p>
+ * We will probably need to setup an hsql behind this, to test some of the pass through methods.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class MySQLDiskCacheUnitTest
+    extends TestCase
+{
+    /**
+     * Verify that we simply return null on get if an optimization is in
+     * progress and the cache is configured to balk on optimization.
+     * <p>
+     * This is a bit tricky since we don't want to have to have a mysql instance
+     * running. Right now this doesn't really test much
+     * @throws SQLException
+     */
+    public void testBalkOnGet() throws SQLException
+    {
+        // SETUP
+        MySQLDiskCacheAttributes attributes = new MySQLDiskCacheAttributes();
+        String tableName = "JCS_TEST";
+        // Just use something that exists
+        attributes.setDriverClassName( "org.hsqldb.jdbcDriver" );
+        attributes.setTableName( tableName );
+        attributes.setBalkDuringOptimization( true );
+        SharedPoolDataSourceFactory dsFactory = new SharedPoolDataSourceFactory();
+        dsFactory.initialize(attributes);
+
+        TableState tableState = new TableState( tableName );
+        tableState.setState( TableState.OPTIMIZATION_RUNNING );
+
+        MySQLDiskCache<String, String> cache = new MySQLDiskCache<>( attributes, dsFactory, tableState,
+        		CompositeCacheManager.getUnconfiguredInstance() );
+
+        // DO WORK
+        Object result = cache.processGet( "myKey" );
+
+        // VERIFY
+        assertNull( "The result should be null", result );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/mysql/util/ScheduleParserUtilUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/mysql/util/ScheduleParserUtilUnitTest.java
new file mode 100644
index 0000000..dad2f2d
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/disk/jdbc/mysql/util/ScheduleParserUtilUnitTest.java
@@ -0,0 +1,131 @@
+package org.apache.commons.jcs3.auxiliary.disk.jdbc.mysql.util;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.text.ParseException;
+import java.util.Date;
+
+import org.apache.commons.jcs3.auxiliary.disk.jdbc.mysql.util.ScheduleParser;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for the schedule parser.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class ScheduleParserUtilUnitTest
+    extends TestCase
+{
+
+    /**
+     * Verify that we get an exception and not a null pointer for null input.
+     */
+    public void testGetDatesWithNullInput()
+    {
+        try
+        {
+            ScheduleParser.createDatesForSchedule( null );
+
+            fail( "Should have thrown an exception" );
+        }
+        catch ( ParseException e )
+        {
+            // expected
+        }
+    }
+
+    /**
+     * Verify that we get an exception and not a null pointer for null input.
+     */
+    public void testGetDateWithNullInput()
+    {
+        try
+        {
+            ScheduleParser.getDateForSchedule( null );
+
+            fail( "Should have thrown an exception" );
+        }
+        catch ( ParseException e )
+        {
+            // expected
+        }
+    }
+
+    /**
+     * Verify that we get one date for one date.
+     * @throws ParseException
+     */
+    public void testGetsDatesSingle()
+        throws ParseException
+    {
+        String schedule = "12:34:56";
+        Date[] dates = ScheduleParser.createDatesForSchedule( schedule );
+
+        assertEquals( "Wrong number of dates returned.", 1, dates.length );
+    }
+    /**
+     * Verify that we get one date for one date.
+     * @throws ParseException
+     */
+    public void testGetsDatesMultiple()
+        throws ParseException
+    {
+        String schedule = "12:34:56,03:51:00,12:34:12";
+        Date[] dates = ScheduleParser.createDatesForSchedule( schedule );
+        //System.out.println( dates );
+        assertEquals( "Wrong number of dates returned.", 3, dates.length );
+    }
+
+    /**
+     * Verify that we get an exception for a single bad date in a list.
+     */
+    public void testGetDatesMalformedNoColon()
+    {
+        try
+        {
+            String schedule = "12:34:56,03:51:00,123234";
+            ScheduleParser.createDatesForSchedule( schedule );
+
+            fail( "Should have thrown an exception for a malformed date" );
+        }
+        catch ( ParseException e )
+        {
+            // expected
+        }
+    }
+    /**
+     * Verify that we get an exception for a schedule that has a non numeric item.
+     */
+    public void testGetDatesMalformedNan()
+    {
+        try
+        {
+            String schedule = "12:34:56,03:51:00,aa:12:12";
+            ScheduleParser.createDatesForSchedule( schedule );
+
+            fail( "Should have thrown an exception for a malformed date" );
+        }
+        catch ( ParseException e )
+        {
+            // expected
+        }
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/lateral/LateralCacheNoWaitFacadeUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/lateral/LateralCacheNoWaitFacadeUnitTest.java
new file mode 100644
index 0000000..df8eb47
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/lateral/LateralCacheNoWaitFacadeUnitTest.java
@@ -0,0 +1,148 @@
+package org.apache.commons.jcs3.auxiliary.lateral;
+
+import org.apache.commons.jcs3.auxiliary.lateral.LateralCache;
+import org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.lateral.LateralCacheNoWait;
+import org.apache.commons.jcs3.auxiliary.lateral.LateralCacheNoWaitFacade;
+import org.apache.commons.jcs3.auxiliary.lateral.behavior.ILateralCacheAttributes;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for LateralCacheNoWaitFacade.
+ */
+public class LateralCacheNoWaitFacadeUnitTest
+    extends TestCase
+{
+    /**
+     * Verify that we can remove an item.
+     */
+    public void testAddThenRemoveNoWait_InList()
+    {
+        // SETUP
+        @SuppressWarnings("unchecked")
+        LateralCacheNoWait<String, String>[] noWaits = new LateralCacheNoWait[0];
+        ILateralCacheAttributes cattr = new LateralCacheAttributes();
+        cattr.setCacheName( "testCache1" );
+
+        LateralCacheNoWaitFacade<String, String> facade = new LateralCacheNoWaitFacade<>( null, noWaits, cattr );
+
+        LateralCache<String, String> cache = new LateralCache<>( cattr );
+        LateralCacheNoWait<String, String> noWait = new LateralCacheNoWait<>( cache );
+
+        // DO WORK
+        facade.addNoWait( noWait );
+
+        // VERIFY
+        assertTrue( "Should be in the list.", facade.containsNoWait( noWait ) );
+
+        // DO WORK
+        facade.removeNoWait( noWait );
+
+        // VERIFY
+        assertEquals( "Should have 0", 0, facade.noWaits.length );
+        assertFalse( "Should not be in the list. ", facade.containsNoWait( noWait ) );
+    }
+
+    /**
+     * Verify that we can remove an item.
+     */
+    public void testAddThenRemoveNoWait_InListSize2()
+    {
+        // SETUP
+        @SuppressWarnings("unchecked")
+        LateralCacheNoWait<String, String>[] noWaits = new LateralCacheNoWait[0];
+        ILateralCacheAttributes cattr = new LateralCacheAttributes();
+        cattr.setCacheName( "testCache1" );
+
+        LateralCacheNoWaitFacade<String, String> facade = new LateralCacheNoWaitFacade<>( null, noWaits, cattr );
+
+        LateralCache<String, String> cache = new LateralCache<>( cattr );
+        LateralCacheNoWait<String, String> noWait = new LateralCacheNoWait<>( cache );
+        LateralCacheNoWait<String, String> noWait2 = new LateralCacheNoWait<>( cache );
+
+        // DO WORK
+        facade.addNoWait( noWait );
+        facade.addNoWait( noWait2 );
+
+        // VERIFY
+        assertEquals( "Should have 2", 2, facade.noWaits.length );
+        assertTrue( "Should be in the list.", facade.containsNoWait( noWait ) );
+        assertTrue( "Should be in the list.", facade.containsNoWait( noWait2 ) );
+
+        // DO WORK
+        facade.removeNoWait( noWait );
+
+        // VERIFY
+        assertEquals( "Should only have 1", 1, facade.noWaits.length );
+        assertFalse( "Should not be in the list. ", facade.containsNoWait( noWait ) );
+        assertTrue( "Should be in the list.", facade.containsNoWait( noWait2 ) );
+    }
+
+    /**
+     * Verify that we can remove an item.
+     */
+    public void testAdd_InList()
+    {
+        // SETUP
+        @SuppressWarnings("unchecked")
+        LateralCacheNoWait<String, String>[] noWaits = new LateralCacheNoWait[0];
+        ILateralCacheAttributes cattr = new LateralCacheAttributes();
+        cattr.setCacheName( "testCache1" );
+
+        LateralCacheNoWaitFacade<String, String> facade = new LateralCacheNoWaitFacade<>( null, noWaits, cattr );
+
+        LateralCache<String, String> cache = new LateralCache<>( cattr );
+        LateralCacheNoWait<String, String> noWait = new LateralCacheNoWait<>( cache );
+
+        // DO WORK
+        facade.addNoWait( noWait );
+        facade.addNoWait( noWait );
+
+        // VERIFY
+        assertTrue( "Should be in the list.", facade.containsNoWait( noWait ) );
+        assertEquals( "Should only have 1", 1, facade.noWaits.length );
+    }
+
+    /**
+     * Verify that we can remove an item.
+     */
+    public void testAddThenRemoveNoWait_NotInList()
+    {
+        // SETUP
+        @SuppressWarnings("unchecked")
+        LateralCacheNoWait<String, String>[] noWaits = new LateralCacheNoWait[0];
+        ILateralCacheAttributes cattr = new LateralCacheAttributes();
+        cattr.setCacheName( "testCache1" );
+
+        LateralCacheNoWaitFacade<String, String> facade = new LateralCacheNoWaitFacade<>( null, noWaits, cattr );
+
+        LateralCache<String, String> cache = new LateralCache<>( cattr );
+        LateralCacheNoWait<String, String> noWait = new LateralCacheNoWait<>( cache );
+
+        // DO WORK
+        facade.removeNoWait( noWait );
+
+        // VERIFY
+        assertFalse( "Should not be in the list.", facade.containsNoWait( noWait ) );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/lateral/http/broadcast/LateralCacheTester.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/lateral/http/broadcast/LateralCacheTester.java
new file mode 100644
index 0000000..1482ffa
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/lateral/http/broadcast/LateralCacheTester.java
@@ -0,0 +1,61 @@
+package org.apache.commons.jcs3.auxiliary.lateral.http.broadcast;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * @author Aaron Smuts
+ * @version 1.0
+ */
+public class LateralCacheTester
+{
+
+    //    /** Description of the Method */
+    //    public static void main( String args[] )
+    //    {
+    //
+    //        String[] servers = {"10.1.17.109", "10.1.17.108"};
+    //
+    //        try
+    //        {
+    //
+    //            //for ( int i=0; i <100; i++ ) {
+    //            String val = "test object value";
+    //            LateralCacheThread dct = new LateralCacheThread( "testTable", "testkey",
+    // val, servers );
+    //            dct.setPriority( Thread.NORM_PRIORITY - 1 );
+    //            dct.start();
+    //
+    //            String val2 = "test object value2";
+    //            LateralCacheThread dct2 = new LateralCacheThread( "testTable", "testkey",
+    // val, servers );
+    //            dct2.setPriority( Thread.NORM_PRIORITY - 1 );
+    //            dct2.start();
+    //            //}
+    //
+    //        }
+    //        catch ( Exception e )
+    //        {
+    //            System.out.println( e.toString() );
+    //        }
+    //
+    //    }
+
+}
+// end class
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPConcurrentRandomTestUtil.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPConcurrentRandomTestUtil.java
new file mode 100644
index 0000000..386d548
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPConcurrentRandomTestUtil.java
@@ -0,0 +1,198 @@
+package org.apache.commons.jcs3.auxiliary.lateral.socket.tcp;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+import java.util.Random;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.LateralTCPService;
+import org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.TCPLateralCacheAttributes;
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+
+/**
+ * @author Aaron Smuts
+ */
+public class LateralTCPConcurrentRandomTestUtil
+    extends TestCase
+{
+    /** Should we write out. */
+    private static boolean isSysOut = false;
+    //private static boolean isSysOut = true;
+
+    /**
+     * Constructor for the TestDiskCache object.
+     *
+     * @param testName
+     */
+    public LateralTCPConcurrentRandomTestUtil( String testName )
+    {
+        super( testName );
+    }
+
+    /**
+     * Test setup
+     */
+    @Override
+    public void setUp()
+    {
+        JCS.setConfigFilename( "/TestTCPLateralCacheConcurrent.ccf" );
+    }
+
+    /**
+     * Randomly adds items to cache, gets them, and removes them. The range
+     * count is more than the size of the memory cache, so items should spool to
+     * disk.
+     * <p>
+     * @param region
+     *            Name of the region to access
+     * @param range
+     * @param numOps
+     * @param testNum
+     *
+     * @throws Exception
+     *                If an error occurs
+     */
+    public void runTestForRegion( String region, int range, int numOps, int testNum )
+        throws Exception
+    {
+        boolean show = true;//false;
+
+        CacheAccess<String, String> cache = JCS.getInstance( region );
+
+        TCPLateralCacheAttributes lattr2 = new TCPLateralCacheAttributes();
+        lattr2.setTcpListenerPort( 1103 );
+        lattr2.setTransmissionTypeName( "TCP" );
+        lattr2.setTcpServer( "localhost:1102" );
+
+        // this service will put and remove using the lateral to
+        // the cache instance above
+        // the cache thinks it is different since the listenerid is different
+        LateralTCPService<String, String> service = new LateralTCPService<>( lattr2 );
+        service.setListenerId( 123456 );
+
+        try
+        {
+            for ( int i = 1; i < numOps; i++ )
+            {
+                Random ran = new Random( i );
+                int n = ran.nextInt( 4 );
+                int kn = ran.nextInt( range );
+                String key = "key" + kn;
+                if ( n == 1 )
+                {
+                    ICacheElement<String, String> element = new CacheElement<>( region, key, region + ":data" + i
+                        + " junk asdfffffffadfasdfasf " + kn + ":" + n );
+                    service.update( element );
+                    if ( show )
+                    {
+                        p( "put " + key );
+                    }
+                }
+                /**/
+                else if ( n == 2 )
+                {
+                    service.remove( region, key );
+                    if ( show )
+                    {
+                        p( "removed " + key );
+                    }
+                }
+                /**/
+                else
+                {
+                    // slightly greater chance of get
+                    try
+                    {
+                        Object obj = service.get( region, key );
+                        if ( show && obj != null )
+                        {
+                            p( obj.toString() );
+                        }
+                    }
+                    catch ( Exception e )
+                    {
+                        // consider failing, some timeouts are expected
+                        e.printStackTrace();
+                    }
+                }
+
+                if ( i % 100 == 0 )
+                {
+                    p( cache.getStats() );
+                }
+
+            }
+            p( "Finished random cycle of " + numOps );
+        }
+        catch ( Exception e )
+        {
+            p( e.toString() );
+            e.printStackTrace( System.out );
+            throw e;
+        }
+
+        CacheAccess<String, String> jcs = JCS.getInstance( region );
+        String key = "testKey" + testNum;
+        String data = "testData" + testNum;
+        jcs.put( key, data );
+        String value = jcs.get( key );
+        assertEquals( "Couldn't put normally.", data, value );
+
+        // make sure the items we can find are in the correct region.
+        for ( int i = 1; i < numOps; i++ )
+        {
+            String keyL = "key" + i;
+            String dataL = jcs.get( keyL );
+            if ( dataL != null )
+            {
+                assertTrue( "Incorrect region detected.", dataL.startsWith( region ) );
+            }
+
+        }
+
+        //Thread.sleep( 1000 );
+
+        //ICacheElement<String, String> element = new CacheElement( region, "abc", "testdata");
+        //service.update( element );
+
+        //Thread.sleep( 2500 );
+        // could be too mcuh going on right now to get ti through, sot he test
+        // might fail.
+        //String value2 = (String) jcs.get( "abc" );
+        //assertEquals( "Couldn't put laterally, could be too much traffic in
+        // queue.", "testdata", value2 );
+
+    }
+
+    /**
+     * @param s string to print
+     */
+    public static void p( String s )
+    {
+        if ( isSysOut )
+        {
+            System.out.println( s );
+        }
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPDiscoveryListenerUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPDiscoveryListenerUnitTest.java
new file mode 100644
index 0000000..f855686
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPDiscoveryListenerUnitTest.java
@@ -0,0 +1,295 @@
+package org.apache.commons.jcs3.auxiliary.lateral.socket.tcp;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+import org.apache.commons.jcs3.engine.logging.MockCacheEventLogger;
+import org.apache.commons.jcs3.auxiliary.lateral.LateralCache;
+import org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.lateral.LateralCacheNoWait;
+import org.apache.commons.jcs3.auxiliary.lateral.LateralCacheNoWaitFacade;
+import org.apache.commons.jcs3.auxiliary.lateral.behavior.ILateralCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.LateralTCPCacheFactory;
+import org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.LateralTCPDiscoveryListener;
+import org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.TCPLateralCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.behavior.ITCPLateralCacheAttributes;
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.engine.control.CompositeCacheManager;
+import org.apache.commons.jcs3.utils.discovery.DiscoveredService;
+import org.apache.commons.jcs3.utils.serialization.StandardSerializer;
+
+import java.util.ArrayList;
+
+/** Test for the listener that observers UDP discovery events. */
+public class LateralTCPDiscoveryListenerUnitTest
+    extends TestCase
+{
+    /** the listener */
+    private LateralTCPDiscoveryListener listener;
+
+    /** the cache factory */
+    private LateralTCPCacheFactory factory;
+
+    /** The cache manager. */
+    private CompositeCacheManager cacheMgr;
+
+    /** The event logger. */
+    protected MockCacheEventLogger cacheEventLogger;
+
+    /** The serializer. */
+    protected IElementSerializer elementSerializer;
+
+    /** Create the listener for testing */
+    @Override
+    protected void setUp() throws Exception
+    {
+        factory = new LateralTCPCacheFactory();
+        factory.initialize();
+
+        cacheMgr = CompositeCacheManager.getInstance();
+        cacheEventLogger = new MockCacheEventLogger();
+        elementSerializer = new StandardSerializer();
+
+        listener = new LateralTCPDiscoveryListener( factory.getName(), cacheMgr );
+    }
+
+    /**
+     * Add a no wait facade.
+     */
+    public void testAddNoWaitFacade_NotInList()
+    {
+        // SETUP
+        String cacheName = "testAddNoWaitFacade_NotInList";
+
+        @SuppressWarnings("unchecked")
+        LateralCacheNoWait<String, String>[] noWaits = new LateralCacheNoWait[0];
+        ILateralCacheAttributes cattr = new LateralCacheAttributes();
+        cattr.setCacheName( cacheName );
+
+        LateralCacheNoWaitFacade<String, String> facade = new LateralCacheNoWaitFacade<>( null, noWaits, cattr );
+
+        // DO WORK
+        listener.addNoWaitFacade( cacheName, facade );
+
+        // VERIFY
+        assertTrue( "Should have the facade.", listener.containsNoWaitFacade( cacheName ) );
+    }
+
+    /**
+     * Add a no wait to a known facade.
+     */
+    public void testAddNoWait_FacadeInList()
+    {
+        // SETUP
+        String cacheName = "testAddNoWaitFacade_FacadeInList";
+
+        @SuppressWarnings("unchecked")
+        LateralCacheNoWait<String, String>[] noWaits = new LateralCacheNoWait[0];
+        ILateralCacheAttributes cattr = new LateralCacheAttributes();
+        cattr.setCacheName( cacheName );
+
+        LateralCacheNoWaitFacade<String, String> facade = new LateralCacheNoWaitFacade<>( null, noWaits, cattr );
+        listener.addNoWaitFacade( cacheName, facade );
+
+        LateralCache<String, String> cache = new LateralCache<>( cattr );
+        LateralCacheNoWait<String, String> noWait = new LateralCacheNoWait<>( cache );
+
+        // DO WORK
+        boolean result = listener.addNoWait( noWait );
+
+        // VERIFY
+        assertTrue( "Should have added the no wait.", result );
+    }
+
+    /**
+     * Add a no wait from an unknown facade.
+     */
+    public void testAddNoWait_FacadeNotInList()
+    {
+        // SETUP
+        String cacheName = "testAddNoWaitFacade_FacadeInList";
+        ILateralCacheAttributes cattr = new LateralCacheAttributes();
+        cattr.setCacheName( cacheName );
+
+        LateralCache<String, String> cache = new LateralCache<>( cattr );
+        LateralCacheNoWait<String, String> noWait = new LateralCacheNoWait<>( cache );
+
+        // DO WORK
+        boolean result = listener.addNoWait( noWait );
+
+        // VERIFY
+        assertFalse( "Should not have added the no wait.", result );
+    }
+
+    /**
+     * Remove a no wait from an unknown facade.
+     */
+    public void testRemoveNoWait_FacadeNotInList()
+    {
+        // SETUP
+        String cacheName = "testRemoveNoWaitFacade_FacadeNotInList";
+        ILateralCacheAttributes cattr = new LateralCacheAttributes();
+        cattr.setCacheName( cacheName );
+
+        LateralCache<String, String> cache = new LateralCache<>( cattr );
+        LateralCacheNoWait<String, String> noWait = new LateralCacheNoWait<>( cache );
+
+        // DO WORK
+        boolean result = listener.removeNoWait( noWait );
+
+        // VERIFY
+        assertFalse( "Should not have removed the no wait.", result );
+    }
+
+    /**
+     * Remove a no wait from a known facade.
+     */
+    public void testRemoveNoWait_FacadeInList_NoWaitNot()
+    {
+        // SETUP
+        String cacheName = "testAddNoWaitFacade_FacadeInList";
+
+        @SuppressWarnings("unchecked")
+        LateralCacheNoWait<String, String>[] noWaits = new LateralCacheNoWait[0];
+        ILateralCacheAttributes cattr = new LateralCacheAttributes();
+        cattr.setCacheName( cacheName );
+
+        LateralCacheNoWaitFacade<String, String> facade = new LateralCacheNoWaitFacade<>( null, noWaits, cattr );
+        listener.addNoWaitFacade( cacheName, facade );
+
+        LateralCache<String, String> cache = new LateralCache<>( cattr );
+        LateralCacheNoWait<String, String> noWait = new LateralCacheNoWait<>( cache );
+
+        // DO WORK
+        boolean result = listener.removeNoWait( noWait );
+
+        // VERIFY
+        assertFalse( "Should not have removed the no wait.", result );
+    }
+
+    /**
+     * Remove a no wait from a known facade.
+     */
+    public void testRemoveNoWait_FacadeInList_NoWaitIs()
+    {
+        // SETUP
+        String cacheName = "testRemoveNoWaitFacade_FacadeInListNoWaitIs";
+
+        @SuppressWarnings("unchecked")
+        LateralCacheNoWait<String, String>[] noWaits = new LateralCacheNoWait[0];
+        ILateralCacheAttributes cattr = new LateralCacheAttributes();
+        cattr.setCacheName( cacheName );
+
+        LateralCacheNoWaitFacade<String, String> facade = new LateralCacheNoWaitFacade<>( null, noWaits, cattr );
+        listener.addNoWaitFacade( cacheName, facade );
+
+        LateralCache<String, String> cache = new LateralCache<>( cattr );
+        LateralCacheNoWait<String, String> noWait = new LateralCacheNoWait<>( cache );
+        listener.addNoWait( noWait );
+
+        // DO WORK
+        boolean result = listener.removeNoWait( noWait );
+
+        // VERIFY
+        assertTrue( "Should have removed the no wait.", result );
+    }
+
+    /**
+     * Add a no wait to a known facade.
+     */
+    public void testAddDiscoveredService_FacadeInList_NoWaitNot()
+    {
+        // SETUP
+        String cacheName = "testAddDiscoveredService_FacadeInList_NoWaitNot";
+
+        ArrayList<String> cacheNames = new ArrayList<>();
+        cacheNames.add( cacheName );
+
+        DiscoveredService service = new DiscoveredService();
+        service.setCacheNames( cacheNames );
+        service.setServiceAddress( "localhost" );
+        service.setServicePort( 9999 );
+
+        // since the no waits are compared by object equality, I have to do this
+        // TODO add an equals method to the noWait.  the problem if is figuring out what to compare.
+        ITCPLateralCacheAttributes lca = new TCPLateralCacheAttributes();
+        lca.setTransmissionType( LateralCacheAttributes.Type.TCP );
+        lca.setTcpServer( service.getServiceAddress() + ":" + service.getServicePort() );
+        lca.setCacheName(cacheName);
+        LateralCacheNoWait<String, String> noWait = factory.createCacheNoWait(lca, cacheEventLogger, elementSerializer);
+        // this is the normal process, the discovery service expects it there
+        cacheMgr.addAuxiliaryCache(factory.getName(), cacheName, noWait);
+
+        @SuppressWarnings("unchecked")
+        LateralCacheNoWait<String, String>[] noWaits = new LateralCacheNoWait[0];
+        ILateralCacheAttributes cattr = new LateralCacheAttributes();
+        cattr.setCacheName( cacheName );
+        LateralCacheNoWaitFacade<String, String> facade = new LateralCacheNoWaitFacade<>( null, noWaits, cattr );
+        listener.addNoWaitFacade( cacheName, facade );
+
+        // DO WORK
+        listener.addDiscoveredService( service );
+
+        // VERIFY
+        assertTrue( "Should have no wait.", listener.containsNoWait( cacheName, noWait ) );
+    }
+
+    /**
+     * Remove a no wait from a known facade.
+     */
+    public void testRemoveDiscoveredService_FacadeInList_NoWaitIs()
+    {
+        // SETUP
+        String cacheName = "testRemoveDiscoveredService_FacadeInList_NoWaitIs";
+
+        ArrayList<String> cacheNames = new ArrayList<>();
+        cacheNames.add( cacheName );
+
+        DiscoveredService service = new DiscoveredService();
+        service.setCacheNames( cacheNames );
+        service.setServiceAddress( "localhost" );
+        service.setServicePort( 9999 );
+
+        // since the no waits are compared by object equality, I have to do this
+        // TODO add an equals method to the noWait.  the problem if is figuring out what to compare.
+        ITCPLateralCacheAttributes lca = new TCPLateralCacheAttributes();
+        lca.setTransmissionType( LateralCacheAttributes.Type.TCP );
+        lca.setTcpServer( service.getServiceAddress() + ":" + service.getServicePort() );
+        lca.setCacheName(cacheName);
+        LateralCacheNoWait<String, String> noWait = factory.createCacheNoWait(lca, cacheEventLogger, elementSerializer);
+        // this is the normal process, the discovery service expects it there
+        cacheMgr.addAuxiliaryCache(factory.getName(), cacheName, noWait);
+
+        @SuppressWarnings("unchecked")
+        LateralCacheNoWait<String, String>[] noWaits = new LateralCacheNoWait[0];
+        ILateralCacheAttributes cattr = new LateralCacheAttributes();
+        cattr.setCacheName( cacheName );
+        LateralCacheNoWaitFacade<String, String> facade = new LateralCacheNoWaitFacade<>( null, noWaits, cattr );
+        listener.addNoWaitFacade( cacheName, facade );
+        listener.addDiscoveredService( service );
+
+        // DO WORK
+        listener.removeDiscoveredService( service );
+
+        // VERIFY
+        assertFalse( "Should not have no wait.", listener.containsNoWait( cacheName, noWait ) );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPFilterRemoveHashCodeUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPFilterRemoveHashCodeUnitTest.java
new file mode 100644
index 0000000..7e028ca
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPFilterRemoveHashCodeUnitTest.java
@@ -0,0 +1,195 @@
+package org.apache.commons.jcs3.auxiliary.lateral.socket.tcp;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+import java.io.Serializable;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.LateralTCPService;
+import org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.TCPLateralCacheAttributes;
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+
+/**
+ * @author Aaron Smuts
+ */
+public class LateralTCPFilterRemoveHashCodeUnitTest
+    extends TestCase
+{
+    /** Does the test print to system out. */
+    private static boolean isSysOut = false;
+
+    /** The port the server will listen to. */
+    private final int serverPort = 2001;
+
+    /**
+     * Constructor for the TestDiskCache object.
+     *
+     * @param testName
+     */
+    public LateralTCPFilterRemoveHashCodeUnitTest( String testName )
+    {
+        super( testName );
+    }
+
+    /**
+     * Test setup
+     */
+    @Override
+    public void setUp()
+    {
+        System.setProperty( "jcs.auxiliary.LTCP.attributes.TcpServers", "localhost:" + serverPort );
+        JCS.setConfigFilename( "/TestTCPLateralRemoveFilter.ccf" );
+    }
+
+    /**
+     *
+     * @throws Exception
+     */
+    public void test()
+        throws Exception
+    {
+        this.runTestForRegion( "region1", 200, 1 );
+    }
+
+    /**
+     * This tests issues tons of puts. It also check to see that a key that was
+     * put in was removed by the clients remove command.
+     *
+     * @param region
+     *            Name of the region to access
+     * @param numOps
+     * @param testNum
+     *
+     * @throws Exception
+     *                If an error occurs
+     */
+    public void runTestForRegion( String region, int numOps, int testNum )
+        throws Exception
+    {
+        CacheAccess<String, Serializable> cache = JCS.getInstance( region );
+
+        Thread.sleep( 100 );
+
+        TCPLateralCacheAttributes lattr2 = new TCPLateralCacheAttributes();
+        lattr2.setTcpListenerPort( 1102 );
+        lattr2.setTransmissionTypeName( "TCP" );
+        lattr2.setTcpServer( "localhost:" + serverPort );
+        lattr2.setIssueRemoveOnPut( true );
+        // should still try to remove
+        lattr2.setAllowPut( false );
+
+        // this service will put and remove using the lateral to
+        // the cache instance above
+        // the cache thinks it is different since the listenerid is different
+        LateralTCPService<String, Serializable> service = new LateralTCPService<>( lattr2 );
+        service.setListenerId( 123456 );
+
+        String keyToBeRemovedOnPut = "test1";
+
+        String keyToNotBeRemovedOnPut = "test2";
+
+        Serializable dataToPassHashCodeCompare = new Serializable()
+        {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public int hashCode()
+            {
+                return 1;
+            }
+        };
+        //String dataToPassHashCodeCompare = "this should be the same and not
+        // get removed.";
+        //p( "dataToPassHashCodeCompare hashcode = " + +
+        // dataToPassHashCodeCompare.hashCode() );
+
+        cache.put( keyToBeRemovedOnPut, "this should get removed." );
+        ICacheElement<String, Serializable> element1 = new CacheElement<>( region, keyToBeRemovedOnPut, region
+            + ":data-this shouldn't get there" );
+        service.update( element1 );
+
+        cache.put( keyToNotBeRemovedOnPut, dataToPassHashCodeCompare );
+        ICacheElement<String, Serializable> element2 = new CacheElement<>( region, keyToNotBeRemovedOnPut, dataToPassHashCodeCompare );
+        service.update( element2 );
+
+        /*
+         * try { for ( int i = 1; i < numOps; i++ ) { Random ran = new Random( i );
+         * int n = ran.nextInt( 4 ); int kn = ran.nextInt( range ); String key =
+         * "key" + kn;
+         *
+         * ICacheElement<String, String> element = new CacheElement( region, key, region +
+         * ":data" + i + " junk asdfffffffadfasdfasf " + kn + ":" + n );
+         * service.update( element ); if ( show ) { p( "put " + key ); }
+         *
+         * if ( i % 100 == 0 ) { System.out.println( cache.getStats() ); }
+         *  } p( "Finished cycle of " + numOps ); } catch ( Exception e ) { p(
+         * e.toString() ); e.printStackTrace( System.out ); throw e; }
+         */
+
+        CacheAccess<String, String> jcs = JCS.getInstance( region );
+        String key = "testKey" + testNum;
+        String data = "testData" + testNum;
+        jcs.put( key, data );
+        String value = jcs.get( key );
+        assertEquals( "Couldn't put normally.", data, value );
+
+        // make sure the items we can find are in the correct region.
+        for ( int i = 1; i < numOps; i++ )
+        {
+            String keyL = "key" + i;
+            String dataL = jcs.get( keyL );
+            if ( dataL != null )
+            {
+                assertTrue( "Incorrect region detected.", dataL.startsWith( region ) );
+            }
+
+        }
+
+        Thread.sleep( 200 );
+
+        Object testObj1 = cache.get( keyToBeRemovedOnPut );
+        p( "test object1 = " + testObj1 );
+        assertNull( "The test object should have been remvoed by a put.", testObj1 );
+
+        Object testObj2 = cache.get( keyToNotBeRemovedOnPut );
+        p( "test object2 = " + testObj2 + " hashCode = " );
+        if ( testObj2 != null )
+        {
+            p( "test2 hashcode = " + +testObj2.hashCode() );
+        }
+        assertNotNull( "This should not have been removed, since the hascode were the same.", testObj2 );
+
+    }
+
+    /**
+     * @param s String to print
+     */
+    public static void p( String s )
+    {
+        if ( isSysOut )
+        {
+            System.out.println( s );
+        }
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPIssueRemoveOnPutUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPIssueRemoveOnPutUnitTest.java
new file mode 100644
index 0000000..e4e9323
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPIssueRemoveOnPutUnitTest.java
@@ -0,0 +1,228 @@
+package org.apache.commons.jcs3.auxiliary.lateral.socket.tcp;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+import java.util.Random;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.LateralTCPService;
+import org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.TCPLateralCacheAttributes;
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+
+/**
+ * Tests the issue remove on put fuctionality.
+ * @author asmuts
+ */
+public class LateralTCPIssueRemoveOnPutUnitTest
+    extends TestCase
+{
+    /** Should log data go to system out. */
+    private static boolean isSysOut = false;
+
+    /** The port the server will listen to. */
+    private final int serverPort = 1118;
+
+    /**
+     * Constructor for the TestDiskCache object.
+     * <p>
+     * @param testName
+     */
+    public LateralTCPIssueRemoveOnPutUnitTest( String testName )
+    {
+        super( testName );
+    }
+
+    /**
+     * Test setup
+     */
+    @Override
+    public void setUp()
+    {
+        System.setProperty( "jcs.auxiliary.LTCP.attributes.TcpServers", "localhost:" + serverPort );
+
+        JCS.setConfigFilename( "/TestTCPLateralIssueRemoveCache.ccf" );
+    }
+
+    /**
+     * @throws Exception
+     */
+    public void testPutLocalPutRemoteGetBusyVerifyRemoved()
+        throws Exception
+    {
+        this.runTestForRegion( "region1", 1, 200, 1 );
+    }
+
+    /**
+     * Verify that a standard put works. Get the cache configured from a file. Create a tcp service
+     * to talk to that cache. Put via the service. Verify that the cache got the data.
+     * <p>
+     * @throws Exception
+     */
+    public void testStandardPut()
+        throws Exception
+    {
+        String region = "region1";
+
+        CacheAccess<String, String> cache = JCS.getInstance( region );
+
+        Thread.sleep( 100 );
+
+        TCPLateralCacheAttributes lattr2 = new TCPLateralCacheAttributes();
+        lattr2.setTcpListenerPort( 1102 );
+        lattr2.setTransmissionTypeName( "TCP" );
+        lattr2.setTcpServer( "localhost:" + serverPort );
+        lattr2.setIssueRemoveOnPut( false );
+        // should still try to remove
+        // lattr2.setAllowPut( false );
+
+        // Using the lateral, this service will put to and remove from
+        // the cache instance above.
+        // The cache thinks it is different since the listenerid is different
+        LateralTCPService<String, String> service = new LateralTCPService<>( lattr2 );
+        service.setListenerId( 123456 );
+
+        String keyToBeRemovedOnPut = "test1_notremoved";
+
+        ICacheElement<String, String> element1 = new CacheElement<>( region, keyToBeRemovedOnPut, region
+            + ":data-this shouldn't get removed, it should get to the cache." );
+        service.update( element1 );
+
+        Thread.sleep( 1000 );
+
+        Object testObj = cache.get( keyToBeRemovedOnPut );
+        p( "testStandardPut, test object = " + testObj );
+        assertNotNull( "The test object should not have been removed by a put.", testObj );
+    }
+
+    /**
+     * This tests issues tons of puts. It also check to see that a key that was put in was removed
+     * by the clients remove command.
+     * <p>
+     * @param region Name of the region to access
+     * @param range
+     * @param numOps
+     * @param testNum
+     * @throws Exception If an error occurs
+     */
+    public void runTestForRegion( String region, int range, int numOps, int testNum )
+        throws Exception
+    {
+
+        boolean show = false;
+
+        CacheAccess<String, String> cache = JCS.getInstance( region );
+
+        Thread.sleep( 100 );
+
+        TCPLateralCacheAttributes lattr2 = new TCPLateralCacheAttributes();
+        lattr2.setTcpListenerPort( 1102 );
+        lattr2.setTransmissionTypeName( "TCP" );
+        lattr2.setTcpServer( "localhost:" + serverPort );
+        lattr2.setIssueRemoveOnPut( true );
+        // should still try to remove
+        lattr2.setAllowPut( false );
+
+        // Using the lateral, this service will put to and remove from
+        // the cache instance above.
+        // The cache thinks it is different since the listenerid is different
+        LateralTCPService<String, String> service = new LateralTCPService<>( lattr2 );
+        service.setListenerId( 123456 );
+
+        String keyToBeRemovedOnPut = "test1";
+        cache.put( keyToBeRemovedOnPut, "this should get removed." );
+
+        ICacheElement<String, String> element1 = new CacheElement<>( region, keyToBeRemovedOnPut, region
+            + ":data-this shouldn't get there" );
+        service.update( element1 );
+
+        try
+        {
+            for ( int i = 1; i < numOps; i++ )
+            {
+                Random ran = new Random( i );
+                int n = ran.nextInt( 4 );
+                int kn = ran.nextInt( range );
+                String key = "key" + kn;
+
+                ICacheElement<String, String> element = new CacheElement<>( region, key, region + ":data" + i
+                    + " junk asdfffffffadfasdfasf " + kn + ":" + n );
+                service.update( element );
+                if ( show )
+                {
+                    p( "put " + key );
+                }
+
+                if (show && i % 100 == 0 )
+                {
+                    System.out.println( cache.getStats() );
+                }
+
+            }
+            p( "Finished cycle of " + numOps );
+        }
+        catch ( Exception e )
+        {
+            p( e.toString() );
+            e.printStackTrace( System.out );
+            throw e;
+        }
+
+        CacheAccess<String, String> jcs = JCS.getInstance( region );
+        String key = "testKey" + testNum;
+        String data = "testData" + testNum;
+        jcs.put( key, data );
+        String value = jcs.get( key );
+        assertEquals( "Couldn't put normally.", data, value );
+
+        // make sure the items we can find are in the correct region.
+        for ( int i = 1; i < numOps; i++ )
+        {
+            String keyL = "key" + i;
+            String dataL = jcs.get( keyL );
+            if ( dataL != null )
+            {
+                assertTrue( "Incorrect region detected.", dataL.startsWith( region ) );
+            }
+
+        }
+
+        Thread.sleep( 200 );
+
+        Object testObj = cache.get( keyToBeRemovedOnPut );
+        p( "runTestForRegion, test object = " + testObj );
+        assertNull( "The test object should have been removed by a put.", testObj );
+
+    }
+
+    /**
+     * @param s String to be printed
+     */
+    public static void p( String s )
+    {
+        if ( isSysOut )
+        {
+            System.out.println( s );
+        }
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPNoDeadLockConcurrentTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPNoDeadLockConcurrentTest.java
new file mode 100644
index 0000000..d19d85e
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPNoDeadLockConcurrentTest.java
@@ -0,0 +1,147 @@
+package org.apache.commons.jcs3.auxiliary.lateral.socket.tcp;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.engine.control.CompositeCacheManager;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.extensions.ActiveTestSuite;
+import junit.framework.Test;
+import junit.framework.TestCase;
+
+/**
+ * Test which exercises the tcp lateral cache. Runs two threads against the
+ * same region and two against other regions.
+ */
+public class LateralTCPNoDeadLockConcurrentTest
+    extends TestCase
+{
+    /**
+     * Constructor for the TestDiskCache object.
+     *
+     * @param testName
+     */
+    public LateralTCPNoDeadLockConcurrentTest( String testName )
+    {
+        super( testName );
+    }
+
+    /**
+     * Main method passes this test to the text test runner.
+     *
+     * @param args
+     */
+    public static void main( String args[] )
+    {
+        String[] testCaseName = { LateralTCPNoDeadLockConcurrentTest.class.getName() };
+        junit.textui.TestRunner.main( testCaseName );
+    }
+
+    /**
+     * A unit test suite for JUnit
+     *
+     * @return The test suite
+     */
+    public static Test suite()
+    {
+
+        System.setProperty( "jcs.auxiliary.LTCP.attributes.PutOnlyMode", "false" );
+
+        ActiveTestSuite suite = new ActiveTestSuite();
+
+        suite.addTest( new LateralTCPConcurrentRandomTestUtil( "testLateralTCPCache1" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "region1", 1, 200, 1 );
+            }
+        } );
+
+        suite.addTest( new LateralTCPConcurrentRandomTestUtil( "testLateralTCPCache2" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "region2", 10000, 12000, 2 );
+            }
+        } );
+
+        suite.addTest( new LateralTCPConcurrentRandomTestUtil( "testLateralTCPCache3" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "region3", 10000, 12000, 3 );
+            }
+        } );
+
+        suite.addTest( new LateralTCPConcurrentRandomTestUtil( "testLateralTCPCache4" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "region3", 10000, 13000, 4 );
+            }
+        } );
+
+        suite.addTest( new LateralTCPConcurrentRandomTestUtil( "testLateralTCPCache5" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "region4", 10000, 11000, 5 );
+            }
+        } );
+
+        return suite;
+    }
+
+    /**
+     * Test setup
+     */
+    @Override
+    public void setUp()
+    {
+        JCS.setConfigFilename( "/TestTCPLateralCacheConcurrent.ccf" );
+    }
+
+    /**
+     * Test tearDown. Dispose of the cache.
+     */
+    @Override
+    public void tearDown()
+    {
+        try
+        {
+            CompositeCacheManager cacheMgr = CompositeCacheManager.getInstance();
+            cacheMgr.shutDown();
+        }
+        catch ( Exception e )
+        {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/TestTCPLateralUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/TestTCPLateralUnitTest.java
new file mode 100644
index 0000000..2f5d1e0
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/TestTCPLateralUnitTest.java
@@ -0,0 +1,372 @@
+package org.apache.commons.jcs3.auxiliary.lateral.socket.tcp;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.engine.control.MockCompositeCacheManager;
+import org.apache.commons.jcs3.utils.timing.SleepUtil;
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.lateral.LateralCommand;
+import org.apache.commons.jcs3.auxiliary.lateral.LateralElementDescriptor;
+import org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.LateralTCPListener;
+import org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.LateralTCPSender;
+import org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.LateralTCPService;
+import org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.TCPLateralCacheAttributes;
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+import org.apache.commons.jcs3.engine.control.CompositeCacheManager;
+import org.apache.commons.jcs3.engine.control.group.GroupAttrName;
+import org.apache.commons.jcs3.engine.control.group.GroupId;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/**
+ * Basic unit tests for the sending and receiving portions of the lateral cache.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class TestTCPLateralUnitTest
+    extends TestCase
+{
+    /**
+     * Test setup
+     */
+    @Override
+    public void setUp()
+    {
+        JCS.setConfigFilename( "/TestTCPLateralCache.ccf" );
+    }
+
+    /**
+     * Make sure we can send a bunch to the listener. This would be better if we could plugin a Mock
+     * CacheManger. The listener will instantiate it on its own. We have to configure one before
+     * that.
+     * <p>
+     * @throws Exception
+     */
+    public void testSimpleSend()
+        throws Exception
+    {
+        // SETUP
+        // force initialization
+        JCS.getInstance( "test" );
+
+        TCPLateralCacheAttributes lac = new TCPLateralCacheAttributes();
+        lac.setTransmissionType( LateralCacheAttributes.Type.TCP );
+        lac.setTcpServer( "localhost" + ":" + 8111 );
+        lac.setTcpListenerPort( 8111 );
+
+        ICompositeCacheManager cacheMgr = CompositeCacheManager.getInstance();
+
+        // start the listener
+        LateralTCPListener<String, String> listener = LateralTCPListener.getInstance( lac, cacheMgr );
+
+        // send to the listener
+        LateralTCPSender lur = new LateralTCPSender( lac );
+
+        // DO WORK
+        int numMes = 10;
+        for ( int i = 0; i < numMes; i++ )
+        {
+            String message = "adsfasasfasfasdasf";
+            CacheElement<String, String> ce = new CacheElement<>( "test", "test", message );
+            LateralElementDescriptor<String, String> led = new LateralElementDescriptor<>( ce );
+            led.command = LateralCommand.UPDATE;
+            led.requesterId = 1;
+            lur.send( led );
+        }
+
+        SleepUtil.sleepAtLeast( numMes * 3 );
+
+        // VERIFY
+        assertEquals( "Should have received " + numMes + " by now.", numMes, listener.getPutCnt() );
+    }
+
+    /**
+     * @throws Exception
+     */
+    public void testReceive()
+        throws Exception
+    {
+        // VERIFY
+        TCPLateralCacheAttributes lattr = new TCPLateralCacheAttributes();
+        lattr.setTcpListenerPort( 1101 );
+        lattr.setTransmissionTypeName( "TCP" );
+        MockCompositeCacheManager cacheMgr = new MockCompositeCacheManager();
+//        System.out.println( "mock cache = " + cacheMgr.getCache( "test" ) );
+
+        LateralTCPListener.getInstance( lattr, cacheMgr );
+
+        TCPLateralCacheAttributes lattr2 = new TCPLateralCacheAttributes();
+        lattr2.setTcpListenerPort( 1102 );
+        lattr2.setTransmissionTypeName( "TCP" );
+        lattr2.setTcpServer( "localhost:1101" );
+
+        LateralTCPService<String, String> service = new LateralTCPService<>( lattr2 );
+        service.setListenerId( 123456 );
+
+        // DO WORK
+        int cnt = 100;
+        for ( int i = 0; i < cnt; i++ )
+        {
+            ICacheElement<String, String> element = new CacheElement<>( "test", "key" + i, "value1" );
+            service.update( element );
+        }
+
+        SleepUtil.sleepAtLeast( 1000 );
+
+        // VERIFY
+        assertEquals( "Didn't get the correct number", cnt, cacheMgr.getCache().getUpdateCount() );
+    }
+
+    /**
+     * Send objects with the same key but different values.
+     * <p>
+     * @throws Exception
+     */
+    public void testSameKeyDifferentObject()
+        throws Exception
+    {
+        // SETUP
+        // setup a listener
+        TCPLateralCacheAttributes lattr = new TCPLateralCacheAttributes();
+        lattr.setTcpListenerPort( 1103 );
+        MockCompositeCacheManager cacheMgr = new MockCompositeCacheManager();
+        CompositeCache<String, String> cache = cacheMgr.getCache( "test" );
+//        System.out.println( "mock cache = " + cache );
+
+        // get the listener started
+        // give it our mock cache manager
+        //LateralTCPListener listener = (LateralTCPListener)
+        LateralTCPListener.getInstance( lattr, cacheMgr );
+
+        // setup a service to talk to the listener started above.
+        TCPLateralCacheAttributes lattr2 = new TCPLateralCacheAttributes();
+        lattr2.setTcpListenerPort( 1104 );
+        lattr2.setTcpServer( "localhost:1103" );
+
+        LateralTCPService<String, String> service = new LateralTCPService<>( lattr2 );
+        service.setListenerId( 123456 );
+
+        // DO WORK
+        ICacheElement<String, String> element = new CacheElement<>( "test", "key", "value1" );
+        service.update( element );
+
+        SleepUtil.sleepAtLeast( 300 );
+
+        ICacheElement<String, String> element2 = new CacheElement<>( "test", "key", "value2" );
+        service.update( element2 );
+
+        SleepUtil.sleepAtLeast( 1000 );
+
+        // VERIFY
+        ICacheElement<String, String> cacheElement = cache.get( "key" );
+        assertEquals( "Didn't get the correct object "+ cacheElement, element2.getVal(), cacheElement.getVal() );
+    }
+
+    /**
+     * Send objects with the same key but different values.
+     * <p>
+     * @throws Exception
+     */
+    public void testSameKeyObjectDifferentValueObject()
+        throws Exception
+    {
+        TCPLateralCacheAttributes lattr = new TCPLateralCacheAttributes();
+        lattr.setTcpListenerPort( 1105 );
+        lattr.setTransmissionTypeName( "TCP" );
+        MockCompositeCacheManager cacheMgr = new MockCompositeCacheManager();
+        CompositeCache<String, String> cache = cacheMgr.getCache( "test" );
+//        System.out.println( "mock cache = " + cache );
+
+        // get the listener started
+        // give it our mock cache manager
+        //LateralTCPListener listener = (LateralTCPListener)
+        LateralTCPListener.getInstance( lattr, cacheMgr );
+
+        TCPLateralCacheAttributes lattr2 = new TCPLateralCacheAttributes();
+        lattr2.setTcpListenerPort( 1106 );
+        lattr2.setTransmissionTypeName( "TCP" );
+        lattr2.setTcpServer( "localhost:1105" );
+
+        LateralTCPService<String, String> service = new LateralTCPService<>( lattr2 );
+        service.setListenerId( 123456 );
+
+        // DO WORK
+        String key = "key";
+        ICacheElement<String, String> element = new CacheElement<>( "test", key, "value1" );
+        service.update( element );
+
+        SleepUtil.sleepAtLeast( 300 );
+
+        ICacheElement<String, String> element2 = new CacheElement<>( "test", key, "value2" );
+        service.update( element2 );
+
+        SleepUtil.sleepAtLeast( 1000 );
+
+        // VERIFY
+        ICacheElement<String, String> cacheElement = cache.get( "key" );
+        assertEquals( "Didn't get the correct object: " + cacheElement , element2.getVal(), cacheElement.getVal() );
+    }
+
+    /**
+     * Create a listener. Add an element to the listeners cache. Setup a service. Try to get from
+     * the service.
+     * <p>
+     * @throws Exception
+     */
+    public void testGet_SendAndReceived()
+        throws Exception
+    {
+        // SETUP
+        // setup a listener
+        TCPLateralCacheAttributes lattr = new TCPLateralCacheAttributes();
+        lattr.setTcpListenerPort( 1107 );
+        MockCompositeCacheManager cacheMgr = new MockCompositeCacheManager();
+        CompositeCache<String, String> cache = cacheMgr.getCache( "test" );
+//        System.out.println( "mock cache = " + cache );
+
+        // get the listener started
+        // give it our mock cache manager
+        LateralTCPListener.getInstance( lattr, cacheMgr );
+
+        // add the item to the listeners cache
+        ICacheElement<String, String> element = new CacheElement<>( "test", "key", "value1" );
+        cache.update( element );
+
+        // setup a service to talk to the listener started above.
+        TCPLateralCacheAttributes lattr2 = new TCPLateralCacheAttributes();
+        lattr2.setTcpListenerPort( 1108 );
+        lattr2.setTcpServer( "localhost:1107" );
+
+        LateralTCPService<String, String> service = new LateralTCPService<>( lattr2 );
+        service.setListenerId( 123456 );
+
+        SleepUtil.sleepAtLeast( 300 );
+
+        // DO WORK
+        ICacheElement<String, String> result = service.get( "test", "key" );
+
+        // VERIFY
+        assertNotNull( "Result should not be null.", result );
+        assertEquals( "Didn't get the correct object", element.getVal(), result.getVal() );
+    }
+
+    /**
+     * Create a listener. Add an element to the listeners cache. Setup a service. Try to get keys from
+     * the service.
+     * <p>
+     * @throws Exception
+     */
+    public void testGetGroupKeys_SendAndReceived()  throws Exception
+    {
+        // SETUP
+        // setup a listener
+        TCPLateralCacheAttributes lattr = new TCPLateralCacheAttributes();
+        lattr.setTcpListenerPort( 1150 );
+        MockCompositeCacheManager cacheMgr = new MockCompositeCacheManager();
+        CompositeCache<GroupAttrName<String>, String> cache = cacheMgr.getCache( "test" );
+//        System.out.println( "mock cache = " + cache );
+
+        // get the listener started
+        // give it our mock cache manager
+        LateralTCPListener.getInstance( lattr, cacheMgr );
+
+        // add the item to the listeners cache
+        GroupAttrName<String> groupKey = new GroupAttrName<>(new GroupId("test", "group"), "key");
+        ICacheElement<GroupAttrName<String>, String> element =
+            new CacheElement<>( "test", groupKey, "value1" );
+        cache.update( element );
+
+        // setup a service to talk to the listener started above.
+        TCPLateralCacheAttributes lattr2 = new TCPLateralCacheAttributes();
+        lattr2.setTcpListenerPort( 1151 );
+        lattr2.setTcpServer( "localhost:1150" );
+
+        LateralTCPService<GroupAttrName<String>, String> service =
+            new LateralTCPService<>( lattr2 );
+        service.setListenerId( 123459 );
+
+        SleepUtil.sleepAtLeast( 500 );
+
+        // DO WORK
+        Set<GroupAttrName<String>> result = service.getKeySet("test");
+
+       // SleepUtil.sleepAtLeast( 5000000 );
+
+        // VERIFY
+        assertNotNull( "Result should not be null.", result );
+        assertEquals( "Didn't get the correct object", "key", result.iterator().next().attrName );
+    }
+
+    /**
+     * Create a listener. Add an element to the listeners cache. Setup a service. Try to get from
+     * the service.
+     * <p>
+     * @throws Exception
+     */
+    public void testGetMatching_WithData()
+        throws Exception
+    {
+        // SETUP
+        // setup a listener
+        TCPLateralCacheAttributes lattr = new TCPLateralCacheAttributes();
+        lattr.setTcpListenerPort( 1108 );
+        MockCompositeCacheManager cacheMgr = new MockCompositeCacheManager();
+        CompositeCache<String, Integer> cache = cacheMgr.getCache( "test" );
+//        System.out.println( "mock cache = " + cache );
+
+        // get the listener started
+        // give it our mock cache manager
+        LateralTCPListener.getInstance( lattr, cacheMgr );
+
+        String keyprefix1 = "MyPrefix1";
+        int numToInsertPrefix1 = 10;
+        // insert with prefix1
+        for ( int i = 0; i < numToInsertPrefix1; i++ )
+        {
+            // add the item to the listeners cache
+            ICacheElement<String, Integer> element = new CacheElement<>( "test", keyprefix1 + String.valueOf( i ), Integer.valueOf( i ) );
+            cache.update( element );
+        }
+
+        // setup a service to talk to the listener started above.
+        TCPLateralCacheAttributes lattr2 = new TCPLateralCacheAttributes();
+        lattr2.setTcpListenerPort( 1108 );
+        lattr2.setTcpServer( "localhost:1108" );
+
+        LateralTCPService<String, Integer> service = new LateralTCPService<>( lattr2 );
+        service.setListenerId( 123456 );
+
+        SleepUtil.sleepAtLeast( 300 );
+
+        // DO WORK
+        Map<String, ICacheElement<String, Integer>> result = service.getMatching( "test", keyprefix1 + ".+" );
+
+        // VERIFY
+        assertNotNull( "Result should not be null.", result );
+        assertEquals( "Wrong number returned 1:", numToInsertPrefix1, result.size() );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/MockRemoteCacheClient.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/MockRemoteCacheClient.java
new file mode 100644
index 0000000..fba96ba
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/MockRemoteCacheClient.java
@@ -0,0 +1,262 @@
+package org.apache.commons.jcs3.auxiliary.remote;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCache;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheClient;
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheListener;
+import org.apache.commons.jcs3.engine.CacheStatus;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheServiceNonLocal;
+import org.apache.commons.jcs3.engine.stats.behavior.IStats;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * Used for testing the no wait.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class MockRemoteCacheClient<K, V>
+    extends AbstractAuxiliaryCache<K, V>
+    implements IRemoteCacheClient<K, V>
+{
+    /** log instance */
+    private static final Log log = LogManager.getLog( MockRemoteCacheClient.class );
+
+    /** List of ICacheElement&lt;K, V&gt; objects passed into update. */
+    public List<ICacheElement<K, V>> updateList = new LinkedList<>();
+
+    /** List of key objects passed into remove. */
+    public List<K> removeList = new LinkedList<>();
+
+    /** status to return. */
+    public CacheStatus status = CacheStatus.ALIVE;
+
+    /** Can setup values to return from get. values must be ICacheElement&lt;K, V&gt; */
+    public Map<K, ICacheElement<K, V>> getSetupMap = new HashMap<>();
+
+    /** Can setup values to return from get. values must be Map&lt;K, ICacheElement&lt;K, V&gt;&gt; */
+    public Map<Set<K>, Map<K, ICacheElement<K, V>>> getMultipleSetupMap =
+        new HashMap<>();
+
+    /** The last service passed to fixCache */
+    public ICacheServiceNonLocal<K, V> fixed;
+
+    /** Attributes. */
+    public RemoteCacheAttributes attributes = new RemoteCacheAttributes();
+
+    /**
+     * Stores the last argument as fixed.
+     */
+    @Override
+    @SuppressWarnings("unchecked") // Don't know how to do this properly
+    public void fixCache( ICacheServiceNonLocal<?, ?> remote )
+    {
+        fixed = (ICacheServiceNonLocal<K, V>)remote;
+    }
+
+    /**
+     * @return long
+     */
+    @Override
+    public long getListenerId()
+    {
+        return 0;
+    }
+
+    /**
+     * @return null
+     */
+    @Override
+    public IRemoteCacheListener<K, V> getListener()
+    {
+        return null;
+    }
+
+    /**
+     * Adds the argument to the updatedList.
+     */
+    @Override
+    public void update( ICacheElement<K, V> ce )
+    {
+        updateList.add( ce );
+    }
+
+    /**
+     * Looks in the getSetupMap for a value.
+     */
+    @Override
+    public ICacheElement<K, V> get( K key )
+    {
+        log.info( "get [" + key + "]" );
+        return getSetupMap.get( key );
+    }
+
+    /**
+     * Gets multiple items from the cache based on the given set of keys.
+     * <p>
+     * @param keys
+     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
+     *         data in cache for any of these keys
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMultiple(Set<K> keys)
+    {
+        log.info( "get [" + keys + "]" );
+        return getMultipleSetupMap.get( keys );
+    }
+
+    /**
+     * Adds the key to the remove list.
+     */
+    @Override
+    public boolean remove( K key )
+    {
+        removeList.add( key );
+        return false;
+    }
+
+    /**
+     * Removes all cached items from the cache.
+     */
+    @Override
+    public void removeAll()
+    {
+        // do nothing
+    }
+
+    /**
+     * Prepares for shutdown.
+     */
+    @Override
+    public void dispose()
+    {
+        // do nothing
+    }
+
+    /**
+     * Returns the current cache size in number of elements.
+     * <p>
+     * @return number of elements
+     */
+    @Override
+    public int getSize()
+    {
+        return 0;
+    }
+
+    /**
+     * Returns the status setup variable.
+     */
+    @Override
+    public CacheStatus getStatus()
+    {
+        return status;
+    }
+
+    /**
+     * Returns the cache name.
+     * <p>
+     * @return usually the region name.
+     */
+    @Override
+    public String getCacheName()
+    {
+        return null;
+    }
+
+    /**
+     * @return null
+     */
+    @Override
+    public Set<K> getKeySet( )
+    {
+        return null;
+    }
+
+    /**
+     * @return null
+     */
+    @Override
+    public IStats getStatistics()
+    {
+        return null;
+    }
+
+    /**
+     * Returns the setup attributes. By default they are not null.
+     */
+    @Override
+    public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
+    {
+        return attributes;
+    }
+
+    /**
+     * Returns the cache stats.
+     * <p>
+     * @return String of important historical information.
+     */
+    @Override
+    public String getStats()
+    {
+        return null;
+    }
+
+    /** @return 0 */
+    @Override
+    public CacheType getCacheType()
+    {
+        return CacheType.REMOTE_CACHE;
+    }
+
+    /**
+     * @param pattern
+     * @return Map
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMatching(String pattern)
+        throws IOException
+    {
+        return new HashMap<>();
+    }
+
+    /**
+     * Nothing important
+     * <p>
+     * @return null
+     */
+    @Override
+    public String getEventLoggingExtraInfo()
+    {
+        return null;
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/MockRemoteCacheListener.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/MockRemoteCacheListener.java
new file mode 100644
index 0000000..bb88c6a
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/MockRemoteCacheListener.java
@@ -0,0 +1,168 @@
+package org.apache.commons.jcs3.auxiliary.remote;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheListener;
+import org.apache.commons.jcs3.auxiliary.remote.server.behavior.RemoteType;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+
+/**
+ * For testing.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class MockRemoteCacheListener<K, V>
+    implements IRemoteCacheListener<K, V>
+{
+    /** Setup the listener id that this will return. */
+    private long listenerId;
+
+    /** Setup the listener ip that this will return. */
+    public String localAddress;
+
+    /** Number of times handlePut was called. */
+    public int putCount;
+
+    /** List of ICacheElements passed to handlePut. */
+    public List<ICacheElement<K, V>> putItems = new LinkedList<>();
+
+    /** List of Serializable objects passed to handleRemove. */
+    public List<K> removedKeys = new LinkedList<>();
+
+    /** Number of times handleRemote was called. */
+    public int removeCount;
+
+    /** The type of remote listener */
+    public RemoteType remoteType = RemoteType.LOCAL;
+
+    /**
+     * @throws IOException
+     */
+    @Override
+    public void dispose()
+        throws IOException
+    {
+        // TODO Auto-generated method stub
+    }
+
+    /**
+     * returns the listener id, which can be setup.
+     * @return listenerId
+     * @throws IOException
+     */
+    @Override
+    public long getListenerId()
+        throws IOException
+    {
+        return listenerId;
+    }
+
+    /**
+     * @return localAddress
+     * @throws IOException
+     */
+    @Override
+    public String getLocalHostAddress()
+        throws IOException
+    {
+        return localAddress;
+    }
+
+    /**
+     * Return the setup remoteType.
+     * @return remoteType
+     * @throws IOException
+     */
+    @Override
+    public RemoteType getRemoteType()
+        throws IOException
+    {
+        return remoteType;
+    }
+
+    /**
+     * Allows you to setup the listener id.
+     * <p>
+     * @param id
+     * @throws IOException
+     */
+    @Override
+    public void setListenerId( long id )
+        throws IOException
+    {
+        listenerId = id;
+    }
+
+    /**
+     * @param cacheName
+     * @throws IOException
+     */
+    @Override
+    public void handleDispose( String cacheName )
+        throws IOException
+    {
+        // TODO Auto-generated method stub
+
+    }
+
+    /**
+     * This increments the put count and adds the item to the putItem list.
+     * <p>
+     * @param item
+     * @throws IOException
+     */
+    @Override
+    public void handlePut( ICacheElement<K, V> item )
+        throws IOException
+    {
+        putCount++;
+        this.putItems.add( item );
+    }
+
+    /**
+     * Increments the remove count and adds the key to the removedKeys list.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @throws IOException
+     */
+    @Override
+    public void handleRemove( String cacheName, K key )
+        throws IOException
+    {
+        removeCount++;
+        removedKeys.add( key );
+    }
+
+    /**
+     * @param cacheName
+     * @throws IOException
+     */
+    @Override
+    public void handleRemoveAll( String cacheName )
+        throws IOException
+    {
+        // TODO Auto-generated method stub
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/MockRemoteCacheService.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/MockRemoteCacheService.java
new file mode 100644
index 0000000..19ba06f
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/MockRemoteCacheService.java
@@ -0,0 +1,241 @@
+package org.apache.commons.jcs3.auxiliary.remote;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheServiceNonLocal;
+
+/**
+ * This is a mock impl of the remote cache service.
+ */
+public class MockRemoteCacheService<K, V>
+    implements ICacheServiceNonLocal<K, V>
+{
+    /** The key last passed to get */
+    public K lastGetKey;
+
+    /** The pattern last passed to get */
+    public String lastGetMatchingPattern;
+
+    /** The keya last passed to getMatching */
+    public Set<K> lastGetMultipleKeys;
+
+    /** The object that was last passed to update. */
+    public ICacheElement<K, V> lastUpdate;
+
+    /** List of updates. */
+    public List<ICacheElement<K, V>> updateRequestList = new ArrayList<>();
+
+    /** List of request ids. */
+    public List<Long> updateRequestIdList = new ArrayList<>();
+
+    /** The key that was last passed to remove. */
+    public K lastRemoveKey;
+
+    /** The cache name that was last passed to removeAll. */
+    public String lastRemoveAllCacheName;
+
+    /**
+     * @param cacheName
+     * @param key
+     * @param requesterId
+     * @return null
+     */
+    @Override
+    public ICacheElement<K, V> get( String cacheName, K key, long requesterId )
+    {
+        lastGetKey = key;
+        return null;
+    }
+
+    /**
+     * @param cacheName
+     * @return empty set
+     */
+    @Override
+    public Set<K> getKeySet( String cacheName )
+    {
+        return new HashSet<>();
+    }
+
+    /**
+     * Set the last remove key.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @param requesterId
+     */
+    @Override
+    public void remove( String cacheName, K key, long requesterId )
+    {
+        lastRemoveKey = key;
+    }
+
+    /**
+     * Set the lastRemoveAllCacheName to the cacheName.
+     */
+    @Override
+    public void removeAll( String cacheName, long requesterId )
+        throws IOException
+    {
+        lastRemoveAllCacheName = cacheName;
+    }
+
+    /**
+     * Set the last update item.
+     * <p>
+     * @param item
+     * @param requesterId
+     */
+    @Override
+    public void update( ICacheElement<K, V> item, long requesterId )
+    {
+        lastUpdate = item;
+        updateRequestList.add( item );
+        updateRequestIdList.add( Long.valueOf( requesterId ) );
+    }
+
+    /**
+     * Do nothing.
+     * <p>
+     * @param cacheName
+     */
+    @Override
+    public void dispose( String cacheName )
+    {
+        return;
+    }
+
+    /**
+     * @param cacheName
+     * @param key
+     * @return null
+     */
+    @Override
+    public ICacheElement<K, V> get( String cacheName, K key )
+    {
+        return get( cacheName, key, 0 );
+    }
+
+    /**
+     * Do nothing.
+     */
+    @Override
+    public void release()
+    {
+        return;
+    }
+
+    /**
+     * Set the last remove key.
+     * <p>
+     * @param cacheName
+     * @param key
+     */
+    @Override
+    public void remove( String cacheName, K key )
+    {
+        lastRemoveKey = key;
+    }
+
+    /**
+     * Set the last remove all cache name.
+     * <p>
+     * @param cacheName
+     */
+    @Override
+    public void removeAll( String cacheName )
+    {
+        lastRemoveAllCacheName = cacheName;
+    }
+
+    /**
+     * Set the last update item.
+     * <p>
+     * @param item
+     */
+    @Override
+    public void update( ICacheElement<K, V> item )
+    {
+        lastUpdate = item;
+    }
+
+    /**
+     * @param cacheName
+     * @param keys
+     * @param requesterId
+     * @return empty map
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys, long requesterId )
+    {
+        lastGetMultipleKeys = keys;
+        return new HashMap<>();
+    }
+
+    /**
+     * @param cacheName
+     * @param keys
+     * @return empty map
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys )
+    {
+        return getMultiple( cacheName, keys, 0 );
+    }
+
+    /**
+     * Returns an empty map. Zombies have no internal data.
+     * <p>
+     * @param cacheName
+     * @param pattern
+     * @return an empty map
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern )
+        throws IOException
+    {
+        return getMatching( cacheName, pattern, 0 );
+    }
+
+    /**
+     * @param cacheName
+     * @param pattern
+     * @param requesterId
+     * @return Map
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern, long requesterId )
+        throws IOException
+    {
+        lastGetMatchingPattern = pattern;
+        return new HashMap<>();
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheClientTester.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheClientTester.java
new file mode 100644
index 0000000..cdcef1f
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheClientTester.java
@@ -0,0 +1,343 @@
+package org.apache.commons.jcs3.auxiliary.remote;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.rmi.Naming;
+import java.rmi.NotBoundException;
+import java.rmi.Remote;
+import java.rmi.registry.Registry;
+import java.rmi.server.ExportException;
+import java.rmi.server.UnicastRemoteObject;
+
+import org.apache.commons.jcs3.access.exception.CacheException;
+import org.apache.commons.jcs3.access.exception.ObjectExistsException;
+import org.apache.commons.jcs3.auxiliary.remote.RemoteUtils;
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheConstants;
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheListener;
+import org.apache.commons.jcs3.auxiliary.remote.server.behavior.RemoteType;
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheObserver;
+import org.apache.commons.jcs3.engine.behavior.ICacheService;
+
+/**
+ * Manual tester.
+ */
+public class RemoteCacheClientTester
+    implements IRemoteCacheListener<String, String>, IRemoteCacheConstants, Remote
+{
+    /** the observer */
+    protected ICacheObserver watch;
+
+    /** the service */
+    protected ICacheService<String, String> cache;
+
+    /** The registry host name. */
+    final String host;
+
+    /** The registry port number. */
+    final int port;
+
+    /** call count */
+    final int count;
+
+    /** Description of the Field */
+    protected static long listenerId = 0;
+
+    /**
+     * Gets the remoteType attribute of the RemoteCacheClientTest object
+     * @return The remoteType value
+     * @throws IOException
+     */
+    @Override
+    public RemoteType getRemoteType()
+        throws IOException
+    {
+        return RemoteType.LOCAL;
+    }
+
+    /**
+     * Constructor for the RemoteCacheClientTest object
+     * @param count
+     * @throws MalformedURLException
+     * @throws NotBoundException
+     * @throws IOException
+     */
+    public RemoteCacheClientTester( int count )
+        throws MalformedURLException, NotBoundException, IOException
+    {
+        this( count, true, true, false );
+    }
+
+    /**
+     * Constructor for the RemoteCacheClientTest object
+     * @param count
+     * @param write
+     * @param read
+     * @param delete
+     * @throws MalformedURLException
+     * @throws NotBoundException
+     * @throws IOException
+     */
+    public RemoteCacheClientTester( int count, boolean write, boolean read, boolean delete )
+        throws MalformedURLException, NotBoundException, IOException
+    {
+        this( "", Registry.REGISTRY_PORT, count, write, read, delete );
+    }
+
+    /**
+     * Constructor for the RemoteCacheClientTest object
+     * @param host
+     * @param port
+     * @param count
+     * @param write
+     * @param read
+     * @param delete
+     * @throws MalformedURLException
+     * @throws NotBoundException
+     * @throws IOException
+     */
+    @SuppressWarnings("unchecked")
+    public RemoteCacheClientTester( String host, int port, int count, boolean write, boolean read, boolean delete )
+        throws MalformedURLException, NotBoundException, IOException
+    {
+        this.count = count;
+        this.host = host;
+        this.port = port;
+        // record export exception
+        Exception ee = null;
+
+        try
+        {
+            // Export this remote object to make it available to receive
+            // incoming calls,
+            // using an anonymous port.
+            UnicastRemoteObject.exportObject( this );
+        }
+        catch ( ExportException e )
+        {
+            // use already exported object; remember exception
+            ee = e;
+            ee.printStackTrace();
+        }
+        String service = System.getProperty( REMOTE_CACHE_SERVICE_NAME );
+
+        if ( service == null )
+        {
+            service = REMOTE_CACHE_SERVICE_VAL;
+        }
+        String registry = RemoteUtils.getNamingURL(host, port, service);
+
+        p( "looking up server " + registry );
+
+        Object obj = Naming.lookup( registry );
+
+        p( "server found" );
+
+        cache = (ICacheService<String, String>) obj;
+        watch = (ICacheObserver) obj;
+
+        p( "subscribing to the server" );
+
+        watch.addCacheListener( "testCache", this );
+        ICacheElement<String, String> cb = new CacheElement<>( "testCache", "testKey", "testVal" );
+
+        for ( int i = 0; i < count; i++ )
+        {
+            cb = new CacheElement<>( "testCache", "" + i, "" + i );
+
+            if ( delete )
+            {
+                p( "deleting a cache item from the server " + i );
+
+                cache.remove( cb.getCacheName(), cb.getKey() );
+            }
+            if ( write )
+            {
+                p( "putting a cache bean to the server " + i );
+
+                try
+                {
+                    cache.update( cb );
+                }
+                catch ( ObjectExistsException oee )
+                {
+                    p( oee.toString() );
+                }
+            }
+            if ( read )
+            {
+                try
+                {
+                    Object val = cache.get( cb.getCacheName(), cb.getKey() );
+                    p( "get " + cb.getKey() + " returns " + val );
+                }
+                catch ( CacheException onfe )
+                {
+                    // nothing
+                }
+            }
+        }
+    }
+
+    /**
+     * @param cb
+     * @throws IOException
+     */
+    @Override
+    public void handlePut( ICacheElement<String, String> cb )
+        throws IOException
+    {
+        p( "handlePut> cb=" + cb );
+    }
+
+    /**
+     * @param cacheName
+     * @param key
+     * @throws IOException
+     */
+    @Override
+    public void handleRemove( String cacheName, String key )
+        throws IOException
+    {
+        p( "handleRemove> cacheName=" + cacheName + ", key=" + key );
+    }
+
+    /**
+     * @param cacheName
+     * @throws IOException
+     */
+    @Override
+    public void handleRemoveAll( String cacheName )
+        throws IOException
+    {
+        p( "handleRemove> cacheName=" + cacheName );
+    }
+
+    /**
+     * @param cacheName
+     * @throws IOException
+     */
+    @Override
+    public void handleDispose( String cacheName )
+        throws IOException
+    {
+        p( "handleDispose> cacheName=" + cacheName );
+    }
+
+    /*
+     * public void handleRelease() throws IOException { p("handleRelease>"); }
+     */
+    /**
+     * The main program for the RemoteCacheClientTest class
+     * @param args The command line arguments
+     * @throws Exception
+     */
+    public static void main( String[] args )
+        throws Exception
+    {
+        int count = 0;
+        boolean read = false;
+        boolean write = false;
+        boolean delete = false;
+
+        for ( int i = 0; i < args.length; i++ )
+        {
+            if ( args[i].startsWith( "-" ) )
+            {
+                if ( !read )
+                {
+                    read = args[i].indexOf( "r" ) != -1;
+                }
+                if ( !write )
+                {
+                    write = args[i].indexOf( "w" ) != -1;
+                }
+                if ( !delete )
+                {
+                    delete = args[i].indexOf( "d" ) != -1;
+                }
+            }
+            else
+            {
+                count = Integer.parseInt( args[i] );
+            }
+        }
+        new RemoteCacheClientTester( count, write, read, delete );
+    }
+
+    /**
+     * Sets the listenerId attribute of the RemoteCacheClientTest object
+     * @param id The new listenerId value
+     * @throws IOException
+     */
+    @Override
+    public void setListenerId( long id )
+        throws IOException
+    {
+        listenerId = id;
+        p( "listenerId = " + id );
+    }
+
+    /**
+     * Gets the listenerId attribute of the RemoteCacheClientTest object
+     * @return The listenerId value
+     * @throws IOException
+     */
+    @Override
+    public long getListenerId()
+        throws IOException
+    {
+        return listenerId;
+    }
+
+    /**
+     * Helper for output, this is an user run test class
+     * @param s
+     */
+    private static void p( String s )
+    {
+        System.out.println( s );
+    }
+
+    /**
+     * @return null
+     * @throws IOException
+     */
+    @Override
+    public String getLocalHostAddress()
+        throws IOException
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    /**
+     * @throws IOException
+     */
+    @Override
+    public void dispose()
+        throws IOException
+    {
+        // TODO Auto-generated method stub
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheListenerUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheListenerUnitTest.java
new file mode 100644
index 0000000..49d5dcd
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheListenerUnitTest.java
@@ -0,0 +1,126 @@
+package org.apache.commons.jcs3.auxiliary.remote;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+import org.apache.commons.jcs3.engine.control.MockCompositeCacheManager;
+import org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.remote.RemoteCacheListener;
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheAttributes;
+import org.apache.commons.jcs3.engine.CacheElementSerialized;
+import org.apache.commons.jcs3.engine.ElementAttributes;
+import org.apache.commons.jcs3.engine.behavior.ICache;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheElementSerialized;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.utils.serialization.StandardSerializer;
+
+/**
+ * Tests for the remote cache listener.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class RemoteCacheListenerUnitTest
+    extends TestCase
+{
+    /**
+     * Create a RemoteCacheListener with a mock cache manager.  Set remove on put to false.
+     * Create a serialized element.  Call put on the listener.
+     * Verify that the deserialized element is in the cache.
+     * <p>
+     * @throws Exception
+     */
+    public void testUpdate_PutOnPut()
+        throws Exception
+    {
+        // SETUP
+        IRemoteCacheAttributes irca = new RemoteCacheAttributes();
+        irca.setRemoveUponRemotePut( false );
+        ICompositeCacheManager cacheMgr = new MockCompositeCacheManager();
+        RemoteCacheListener<String, String> listener = new RemoteCacheListener<>( irca, cacheMgr, new StandardSerializer() );
+
+        String cacheName = "testName";
+        String key = "key";
+        String value = "value fdsadf dsafdsa fdsaf dsafdsaf dsafdsaf dsaf dsaf dsaf dsafa dsaf dsaf dsafdsaf";
+        IElementAttributes attr = new ElementAttributes();
+        attr.setMaxLife(34);
+
+        IElementSerializer elementSerializer = new StandardSerializer();
+
+        ICacheElementSerialized<String, String> element =
+            new CacheElementSerialized<>( cacheName, key, elementSerializer
+            .serialize( value ), attr );
+
+        // DO WORK
+        listener.handlePut( element );
+
+        // VERIFY
+        ICache<String, String> cache = cacheMgr.getCache( cacheName );
+        ICacheElement<String, String> after = cache.get( key );
+
+        assertNotNull( "Should have a deserialized object.", after );
+        assertEquals( "Values should be the same.", value, after.getVal() );
+        assertEquals( "Attributes should be the same.", attr.getMaxLife(), after
+            .getElementAttributes().getMaxLife() );
+        assertEquals( "Keys should be the same.", key, after.getKey() );
+        assertEquals( "Cache name should be the same.", cacheName, after.getCacheName() );
+    }
+
+    /**
+     * Create a RemoteCacheListener with a mock cache manager.  Set remove on put to true.
+     * Create a serialized element.  Call put on the listener.
+     * Verify that the deserialized element is not in the cache.
+     * <p>
+     * @throws Exception
+     */
+    public void testUpdate_RemoveOnPut()
+        throws Exception
+    {
+        // SETUP
+        IRemoteCacheAttributes irca = new RemoteCacheAttributes();
+        irca.setRemoveUponRemotePut( true );
+        ICompositeCacheManager cacheMgr = new MockCompositeCacheManager();
+        RemoteCacheListener<String, String> listener = new RemoteCacheListener<>( irca, cacheMgr, new StandardSerializer() );
+
+        String cacheName = "testName";
+        String key = "key";
+        String value = "value fdsadf dsafdsa fdsaf dsafdsaf dsafdsaf dsaf dsaf dsaf dsafa dsaf dsaf dsafdsaf";
+        IElementAttributes attr = new ElementAttributes();
+        attr.setMaxLife(34);
+
+        IElementSerializer elementSerializer = new StandardSerializer();
+
+        ICacheElementSerialized<String, String> element =
+            new CacheElementSerialized<>( cacheName, key, elementSerializer
+            .serialize( value ), attr );
+
+        // DO WORK
+        listener.handlePut( element );
+
+        // VERIFY
+        ICache<String, String> cache = cacheMgr.getCache( cacheName );
+        ICacheElement<String, String> after = cache.get( key );
+
+        assertNull( "Should not have a deserialized object since remove on put is true.", after );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheNoWaitFacadeUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheNoWaitFacadeUnitTest.java
new file mode 100644
index 0000000..5968936
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheNoWaitFacadeUnitTest.java
@@ -0,0 +1,60 @@
+package org.apache.commons.jcs3.auxiliary.remote;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.jcs3.auxiliary.remote.RemoteCache;
+import org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.remote.RemoteCacheNoWait;
+import org.apache.commons.jcs3.auxiliary.remote.RemoteCacheNoWaitFacade;
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheAttributes;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for RemoteCacheNoWaitFacade.
+ */
+public class RemoteCacheNoWaitFacadeUnitTest
+    extends TestCase
+{
+    /**
+     * Verify that we can add an item.
+     */
+    public void testAddNoWait_InList()
+    {
+        // SETUP
+        List<RemoteCacheNoWait<String, String>> noWaits = new ArrayList<>();
+        IRemoteCacheAttributes cattr = new RemoteCacheAttributes();
+        cattr.setCacheName( "testCache1" );
+
+        RemoteCache<String, String> client = new RemoteCache<>(cattr, null, null, null);
+        RemoteCacheNoWait<String, String> noWait = new RemoteCacheNoWait<>( client );
+        noWaits.add( noWait );
+
+        RemoteCacheNoWaitFacade<String, String> facade = new RemoteCacheNoWaitFacade<>(noWaits, cattr, null, null, null );
+
+        // VERIFY
+        assertEquals( "Should have one entry.", 1, facade.noWaits.size() );
+        assertTrue( "Should be in the list.", facade.noWaits.contains( noWait ) );
+        assertSame( "Should have same facade.", facade, ((RemoteCache<String, String>)facade.noWaits.get(0).getRemoteCache()).getFacade() );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheNoWaitUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheNoWaitUnitTest.java
new file mode 100644
index 0000000..44a121c
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheNoWaitUnitTest.java
@@ -0,0 +1,212 @@
+package org.apache.commons.jcs3.auxiliary.remote;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.utils.timing.SleepUtil;
+import org.apache.commons.jcs3.auxiliary.remote.RemoteCacheNoWait;
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.CacheStatus;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for the remote cache no wait. The no wait manages a queue on top of the client.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class RemoteCacheNoWaitUnitTest
+    extends TestCase
+{
+    /**
+     * Simply verify that the client gets updated via the no wait.
+     * <p>
+     * @throws Exception
+     */
+    public void testUpdate()
+        throws Exception
+    {
+        // SETUP
+        MockRemoteCacheClient<String, String> client = new MockRemoteCacheClient<>();
+        RemoteCacheNoWait<String, String> noWait = new RemoteCacheNoWait<>( client );
+
+        ICacheElement<String, String> element = new CacheElement<>( "testUpdate", "key", "value" );
+
+        // DO WORK
+        noWait.update( element );
+
+        // VERIFY
+        SleepUtil.sleepAtLeast( 10 );
+
+        assertEquals( "Wrong number updated.", 1, client.updateList.size() );
+        assertEquals( "Wrong element", element, client.updateList.get( 0 ) );
+    }
+
+    /**
+     * Simply verify that the client get is called from the no wait.
+     * <p>
+     * @throws Exception
+     */
+    public void testGet()
+        throws Exception
+    {
+        // SETUP
+        MockRemoteCacheClient<String, String> client = new MockRemoteCacheClient<>();
+        RemoteCacheNoWait<String, String> noWait = new RemoteCacheNoWait<>( client );
+
+        ICacheElement<String, String> input = new CacheElement<>( "testUpdate", "key", "value" );
+        client.getSetupMap.put( "key", input );
+
+        // DO WORK
+        ICacheElement<String, String> result = noWait.get( "key" );
+
+        // VERIFY
+        assertEquals( "Wrong element", input, result );
+    }
+
+    /**
+     * Simply verify that the client getMultiple is called from the no wait.
+     * <p>
+     * @throws Exception
+     */
+    public void testGetMultiple()
+        throws Exception
+    {
+        // SETUP
+        MockRemoteCacheClient<String, String> client = new MockRemoteCacheClient<>();
+        RemoteCacheNoWait<String, String> noWait = new RemoteCacheNoWait<>( client );
+
+        ICacheElement<String, String> inputElement = new CacheElement<>( "testUpdate", "key", "value" );
+        Map<String, ICacheElement<String, String>> inputMap = new HashMap<>();
+        inputMap.put( "key", inputElement );
+
+        Set<String> keys = new HashSet<>();
+        keys.add( "key" );
+
+        client.getMultipleSetupMap.put( keys, inputMap );
+
+        // DO WORK
+        Map<String, ICacheElement<String, String>> result = noWait.getMultiple( keys );
+
+        // VERIFY
+        assertEquals( "elements map", inputMap, result );
+    }
+
+    /**
+     * Simply verify that the client gets updated via the no wait.
+     * <p>
+     * @throws Exception
+     */
+    public void testRemove()
+        throws Exception
+    {
+        // SETUP
+        MockRemoteCacheClient<String, String> client = new MockRemoteCacheClient<>();
+        RemoteCacheNoWait<String, String> noWait = new RemoteCacheNoWait<>( client );
+
+        String input = "MyKey";
+
+        // DO WORK
+        noWait.remove( input );
+
+        SleepUtil.sleepAtLeast( 10 );
+
+        // VERIFY
+        assertEquals( "Wrong number updated.", 1, client.removeList.size() );
+        assertEquals( "Wrong key", input, client.removeList.get( 0 ) );
+    }
+
+    /**
+     * Simply verify that the client status is returned in the stats.
+     * <p>
+     * @throws Exception
+     */
+    public void testGetStats()
+        throws Exception
+    {
+        // SETUP
+        MockRemoteCacheClient<String, String> client = new MockRemoteCacheClient<>();
+        client.status = CacheStatus.ALIVE;
+        RemoteCacheNoWait<String, String> noWait = new RemoteCacheNoWait<>( client );
+
+        // DO WORK
+        String result = noWait.getStats();
+
+        // VERIFY
+        assertTrue( "Status should contain 'ALIVE'", result.indexOf( "ALIVE" ) != -1 );
+    }
+
+    /**
+     * Simply verify that we get a status of error if the cache is in error..
+     * <p>
+     * @throws Exception
+     */
+    public void testGetStatus_error()
+        throws Exception
+    {
+        // SETUP
+        MockRemoteCacheClient<String, String> client = new MockRemoteCacheClient<>();
+        client.status = CacheStatus.ERROR;
+        RemoteCacheNoWait<String, String> noWait = new RemoteCacheNoWait<>( client );
+
+        // DO WORK
+        CacheStatus result = noWait.getStatus();
+
+        // VERIFY
+        assertEquals( "Wrong status", CacheStatus.ERROR, result );
+    }
+
+    /**
+     * Simply verify that the serviced supplied to fix is passed onto the client. Verify that the
+     * original event queue is destroyed. A new event queue willbe plugged in on fix.
+     * <p>
+     * @throws Exception
+     */
+    public void testFixCache()
+        throws Exception
+    {
+        // SETUP
+        MockRemoteCacheClient<String, String> client = new MockRemoteCacheClient<>();
+        client.status = CacheStatus.ALIVE;
+        RemoteCacheNoWait<String, String> noWait = new RemoteCacheNoWait<>( client );
+
+        MockRemoteCacheService<String, String> service = new MockRemoteCacheService<>();
+
+        ICacheElement<String, String> element = new CacheElement<>( "testUpdate", "key", "value" );
+
+        // DO WORK
+        noWait.update( element );
+        SleepUtil.sleepAtLeast( 10 );
+        // ICacheEventQueue<String, String> originalQueue = noWait.getCacheEventQueue();
+
+        noWait.fixCache( service );
+
+        noWait.update( element );
+        SleepUtil.sleepAtLeast( 10 );
+
+        // VERIFY
+        assertEquals( "Wrong status", service, client.fixed );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheUnitTest.java
new file mode 100644
index 0000000..3ab8ce8
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/RemoteCacheUnitTest.java
@@ -0,0 +1,298 @@
+package org.apache.commons.jcs3.auxiliary.remote;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.HashSet;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.jcs3.auxiliary.MockCacheEventLogger;
+import org.apache.commons.jcs3.auxiliary.remote.RemoteCache;
+import org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.remote.RemoteCacheMonitor;
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheAttributes;
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.ZombieCacheServiceNonLocal;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheElementSerialized;
+import org.apache.commons.jcs3.utils.serialization.SerializationConversionUtil;
+
+/**
+ * Unit Tests for the Remote Cache.
+ */
+public class RemoteCacheUnitTest
+    extends TestCase
+{
+    private IRemoteCacheAttributes cattr;
+    private MockRemoteCacheService<String, String> service;
+    private MockRemoteCacheListener<String, String> listener;
+    private RemoteCacheMonitor monitor;
+
+    /**
+     * @see junit.framework.TestCase#setUp()
+     */
+    @Override
+    protected void setUp() throws Exception
+    {
+        super.setUp();
+        cattr = new RemoteCacheAttributes();
+        service = new MockRemoteCacheService<>();
+        listener = new MockRemoteCacheListener<>();
+        monitor = new RemoteCacheMonitor();
+    }
+
+    /**
+     * Verify that the remote service update method is called. The remote cache serializes the object
+     * first.
+     * <p>
+     * @throws Exception
+     */
+    public void testUpdate()
+        throws Exception
+    {
+        // SETUP
+        long listenerId = 123;
+        listener.setListenerId( listenerId );
+
+        RemoteCache<String, String> remoteCache = new RemoteCache<>( cattr, service, listener, monitor );
+
+        String cacheName = "testUpdate";
+
+        // DO WORK
+        ICacheElement<String, String> element = new CacheElement<>( cacheName, "key", "value" );
+        remoteCache.update( element );
+
+        // VERIFY
+        assertTrue( "The element should be in the serialized wrapper.",
+                    service.lastUpdate instanceof ICacheElementSerialized );
+        ICacheElement<String, String> result = SerializationConversionUtil
+            .getDeSerializedCacheElement( (ICacheElementSerialized<String, String>) service.lastUpdate, remoteCache
+                .getElementSerializer() );
+        assertEquals( "Wrong element updated.", element.getVal(), result.getVal() );
+        assertEquals( "Wrong listener id.", Long.valueOf( listenerId ), service.updateRequestIdList.get( 0 ) );
+    }
+
+    /**
+     * Verify that when we call fix events queued in the zombie are propagated to the new service.
+     * <p>
+     * @throws Exception
+     */
+    public void testUpdateZombieThenFix()
+        throws Exception
+    {
+        // SETUP
+        ZombieCacheServiceNonLocal<String, String> zombie = new ZombieCacheServiceNonLocal<>( 10 );
+
+        // set the zombie
+        RemoteCache<String, String> remoteCache = new RemoteCache<>( cattr, zombie, listener, monitor );
+
+        String cacheName = "testUpdate";
+
+        // DO WORK
+        ICacheElement<String, String> element = new CacheElement<>( cacheName, "key", "value" );
+        remoteCache.update( element );
+        // set the new service, this should call propagate
+        remoteCache.fixCache( service );
+
+        // VERIFY
+        assertTrue( "The element should be in the serialized warapper.",
+                    service.lastUpdate instanceof ICacheElementSerialized );
+        ICacheElement<String, String> result = SerializationConversionUtil
+            .getDeSerializedCacheElement( (ICacheElementSerialized<String, String>) service.lastUpdate, remoteCache
+                .getElementSerializer() );
+        assertEquals( "Wrong element updated.", element.getVal(), result.getVal() );
+    }
+
+    /**
+     * Verify event log calls.
+     * <p>
+     * @throws Exception
+     */
+    public void testUpdate_simple()
+        throws Exception
+    {
+        RemoteCache<String, String> remoteCache = new RemoteCache<>( cattr, service, listener, monitor );
+
+        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
+        remoteCache.setCacheEventLogger( cacheEventLogger );
+
+        ICacheElement<String, String> item = new CacheElement<>( "region", "key", "value" );
+
+        // DO WORK
+        remoteCache.update( item );
+
+        // VERIFY
+        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
+        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
+    }
+
+    /**
+     * Verify event log calls.
+     * <p>
+     * @throws Exception
+     */
+    public void testGet_simple()
+        throws Exception
+    {
+        RemoteCache<String, String> remoteCache = new RemoteCache<>( cattr, service, listener, monitor );
+
+        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
+        remoteCache.setCacheEventLogger( cacheEventLogger );
+
+        // DO WORK
+        remoteCache.get( "key" );
+
+        // VERIFY
+        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
+        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
+    }
+
+    /**
+     * Verify event log calls.
+     * <p>
+     * @throws Exception
+     */
+    public void testGetMultiple_simple()
+        throws Exception
+    {
+        RemoteCache<String, String> remoteCache = new RemoteCache<>( cattr, service, listener, monitor );
+
+        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
+        remoteCache.setCacheEventLogger( cacheEventLogger );
+
+        // DO WORK
+        remoteCache.getMultiple( new HashSet<>() );
+
+        // VERIFY
+        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
+        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
+    }
+
+    /**
+     * Verify event log calls.
+     * <p>
+     * @throws Exception
+     */
+    public void testRemove_simple()
+        throws Exception
+    {
+        RemoteCache<String, String> remoteCache = new RemoteCache<>( cattr, service, listener, monitor );
+
+        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
+        remoteCache.setCacheEventLogger( cacheEventLogger );
+
+        // DO WORK
+        remoteCache.remove( "key" );
+
+        // VERIFY
+        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
+        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
+    }
+
+    /**
+     * Verify event log calls.
+     * <p>
+     * @throws Exception
+     */
+    public void testRemoveAll_simple()
+        throws Exception
+    {
+        RemoteCache<String, String> remoteCache = new RemoteCache<>( cattr, service, listener, monitor );
+
+        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
+        remoteCache.setCacheEventLogger( cacheEventLogger );
+
+        // DO WORK
+        remoteCache.remove( "key" );
+
+        // VERIFY
+        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
+        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
+    }
+
+    /**
+     * Verify event log calls.
+     * <p>
+     * @throws Exception
+     */
+    public void testGetMatching_simple()
+        throws Exception
+    {
+        // SETUP
+        String pattern = "adsfasdfasd.?";
+
+        RemoteCache<String, String> remoteCache = new RemoteCache<>( cattr, service, listener, monitor );
+
+        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
+        remoteCache.setCacheEventLogger( cacheEventLogger );
+
+        // DO WORK
+        Map<String, ICacheElement<String, String>> result = remoteCache.getMatching( pattern );
+
+        // VERIFY
+        assertNotNull( "Should have a map", result );
+        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
+        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
+    }
+
+    /**
+     * Verify event log calls.
+     * <p>
+     * @throws Exception
+     */
+    public void testDispose_simple()
+        throws Exception
+    {
+        RemoteCache<String, String> remoteCache = new RemoteCache<>( cattr, service, listener, monitor );
+
+        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
+        remoteCache.setCacheEventLogger( cacheEventLogger );
+
+        // DO WORK
+        remoteCache.dispose( );
+
+        // VERIFY
+        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
+        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
+    }
+
+    /**
+     * Verify that there is no problem if there is no listener.
+     * <p>
+     * @throws Exception
+     */
+    public void testDispose_nullListener()
+        throws Exception
+    {
+        // SETUP
+        RemoteCache<String, String> remoteCache = new RemoteCache<>( cattr, service, null, monitor );
+
+        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
+        remoteCache.setCacheEventLogger( cacheEventLogger );
+
+        // DO WORK
+        remoteCache.dispose( );
+
+        // VERIFY
+        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
+        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/RemoteUtilsUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/RemoteUtilsUnitTest.java
new file mode 100644
index 0000000..dfec2f7
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/RemoteUtilsUnitTest.java
@@ -0,0 +1,68 @@
+package org.apache.commons.jcs3.auxiliary.remote;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.rmi.registry.Registry;
+
+import org.apache.commons.jcs3.auxiliary.remote.RemoteLocation;
+import org.apache.commons.jcs3.auxiliary.remote.RemoteUtils;
+
+import junit.framework.TestCase;
+
+/**
+ * Simple tests for remote utils. It is difficult to verify most of the things is does.
+ *<p>
+ * @author Aaron Smuts
+ */
+public class RemoteUtilsUnitTest
+    extends TestCase
+{
+    /**
+     * Call create registry.
+     * <p>
+     * The exception is in the security manager setting.
+     */
+    public void testCreateRegistry()
+    {
+        Registry registry = RemoteUtils.createRegistry( 1102 );
+        assertNotNull("Registry should not be null", registry);
+    }
+
+    public void testGetNamingURL()
+    {
+        assertEquals("//host:1/servicename", RemoteUtils.getNamingURL("host",1,"servicename"));
+        assertEquals("//127.0.0.1:2/servicename", RemoteUtils.getNamingURL("127.0.0.1",2,"servicename"));
+        assertEquals("//[0:0:0:0:0:0:0:1%251]:3/servicename", RemoteUtils.getNamingURL("0:0:0:0:0:0:0:1%1",3,"servicename"));
+    }
+
+    public void testParseServerAndPort()
+    {
+        RemoteLocation loc = RemoteLocation.parseServerAndPort("server1:1234");
+        assertEquals("server1", loc.getHost());
+        assertEquals(1234, loc.getPort());
+
+        loc = RemoteLocation.parseServerAndPort("  server2  :  4567  ");
+        assertEquals("server2", loc.getHost());
+        assertEquals(4567, loc.getPort());
+
+        loc = RemoteLocation.parseServerAndPort("server2  :  port");
+        assertNull(loc);
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/TestRemoteCache.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/TestRemoteCache.java
new file mode 100644
index 0000000..79ba83e
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/TestRemoteCache.java
@@ -0,0 +1,145 @@
+package org.apache.commons.jcs3.auxiliary.remote;
+
+import java.util.Properties;
+
+import org.apache.commons.jcs3.auxiliary.MockCacheEventLogger;
+import org.apache.commons.jcs3.engine.control.MockCompositeCacheManager;
+import org.apache.commons.jcs3.engine.control.MockElementSerializer;
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCache;
+import org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory;
+import org.apache.commons.jcs3.auxiliary.remote.RemoteCacheManager;
+import org.apache.commons.jcs3.auxiliary.remote.RemoteUtils;
+import org.apache.commons.jcs3.auxiliary.remote.server.RemoteCacheServerFactory;
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/**
+ * @author Aaron SMuts
+ */
+public class TestRemoteCache
+    extends TestCase
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog( TestRemoteCache.class );
+
+    /**
+     * Start the cache.
+     */
+    public TestRemoteCache()
+    {
+        super();
+        try
+        {
+            System.out.println( "main> creating registry on the localhost" );
+            RemoteUtils.createRegistry( 1101 );
+            Properties config = RemoteUtils.loadProps("/TestRemoteServer.ccf");
+
+            RemoteCacheServerFactory.startup( "localhost", 1101, config);
+        }
+        catch ( Exception e )
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Test setup
+     */
+    @Override
+    public void setUp()
+    {
+        JCS.setConfigFilename( "/TestRemoteClient.ccf" );
+    }
+
+    /**
+     * @throws Exception
+     *
+     *
+     */
+    public void skiptestSimpleSend()
+        throws Exception
+    {
+        log.info( "testSimpleSend" );
+
+        CacheAccess<String, String> cache = JCS.getInstance( "testCache" );
+
+        log.info( "cache = " + cache );
+
+        for ( int i = 0; i < 1000; i++ )
+        {
+//            System.out.println( "puttting " + i );
+            cache.put( "key" + i, "data" + i );
+//            System.out.println( "put " + i );
+            log.info( "put " + i );
+        }
+    }
+
+    /**
+     * @throws Exception
+     */
+    public void testService()
+        throws Exception
+    {
+
+        Thread.sleep( 100 );
+
+        ICompositeCacheManager cacheMgr = new MockCompositeCacheManager();
+
+        RemoteCacheAttributes rca = new RemoteCacheAttributes();
+        rca.setRemoteLocation( "localhost", 1101 );
+        rca.setCacheName( "testCache" );
+
+        RemoteCacheFactory factory = new RemoteCacheFactory();
+        factory.initialize();
+        RemoteCacheManager mgr = factory.getManager( rca, cacheMgr, new MockCacheEventLogger(), new MockElementSerializer() );
+        AuxiliaryCache<String, String> cache = mgr.getCache( rca );
+
+        int numMes = 100;
+        for ( int i = 0; i < numMes; i++ )
+        {
+            String message = "adsfasasfasfasdasf";
+            CacheElement<String, String> ce = new CacheElement<>( "key" + 1, "data" + i, message );
+            cache.update( ce );
+//            System.out.println( "put " + ce );
+        }
+
+        // Thread.sleep( 2000 );
+
+        /*
+         * // the receiver instance. JCS cacheReceiver = JCS.getInstance(
+         * "testCache" );
+         *
+         * log.info( "cache = " + cache );
+         *
+         * for ( int i = 0; i < numMes; i++ ) { System.out.println( "getting " +
+         * i ); Object data = cacheReceiver.get( "key" + i );
+         * System.out.println( i + " = " + data ); }
+         */
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/ZombieRemoteCacheServiceUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/ZombieRemoteCacheServiceUnitTest.java
new file mode 100644
index 0000000..ead3b81
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/ZombieRemoteCacheServiceUnitTest.java
@@ -0,0 +1,128 @@
+package org.apache.commons.jcs3.auxiliary.remote;
+
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.ZombieCacheServiceNonLocal;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for the zombie remote cache service.
+ */
+public class ZombieRemoteCacheServiceUnitTest
+    extends TestCase
+{
+    /**
+     * Verify that an update event gets added and then is sent to the service passed to propagate.
+     * <p>
+     * @throws Exception
+     */
+    public void testUpdateThenWalk()
+        throws Exception
+    {
+        // SETUP
+        MockRemoteCacheService<String, String> service = new MockRemoteCacheService<>();
+
+        ZombieCacheServiceNonLocal<String, String> zombie = new ZombieCacheServiceNonLocal<>( 10 );
+
+        String cacheName = "testUpdate";
+
+        // DO WORK
+        ICacheElement<String, String> element = new CacheElement<>( cacheName, "key", "value" );
+        zombie.update( element, 123l );
+        zombie.propagateEvents( service );
+
+        // VERIFY
+        assertEquals( "Updated element is not as expected.", element, service.lastUpdate );
+    }
+
+    /**
+     * Verify that nothing is added if the max is set to 0.
+     * <p>
+     * @throws Exception
+     */
+    public void testUpdateThenWalk_zeroSize()
+        throws Exception
+    {
+        // SETUP
+        MockRemoteCacheService<String, String> service = new MockRemoteCacheService<>();
+
+        ZombieCacheServiceNonLocal<String, String> zombie = new ZombieCacheServiceNonLocal<>( 0 );
+
+        String cacheName = "testUpdate";
+
+        // DO WORK
+        ICacheElement<String, String> element = new CacheElement<>( cacheName, "key", "value" );
+        zombie.update( element, 123l );
+        zombie.propagateEvents( service );
+
+        // VERIFY
+        assertNull( "Nothing should have been put to the service.", service.lastUpdate );
+    }
+
+    /**
+     * Verify that a remove event gets added and then is sent to the service passed to propagate.
+     * <p>
+     * @throws Exception
+     */
+    public void testRemoveThenWalk()
+        throws Exception
+    {
+        // SETUP
+        MockRemoteCacheService<String, String> service = new MockRemoteCacheService<>();
+
+        ZombieCacheServiceNonLocal<String, String> zombie = new ZombieCacheServiceNonLocal<>( 10 );
+
+        String cacheName = "testRemoveThenWalk";
+        String key = "myKey";
+
+        // DO WORK
+        zombie.remove( cacheName, key, 123l );
+        zombie.propagateEvents( service );
+
+        // VERIFY
+        assertEquals( "Updated element is not as expected.", key, service.lastRemoveKey );
+    }
+
+    /**
+     * Verify that a removeAll event gets added and then is sent to the service passed to propagate.
+     * <p>
+     * @throws Exception
+     */
+    public void testRemoveAllThenWalk()
+        throws Exception
+    {
+        // SETUP
+        MockRemoteCacheService<String, String> service = new MockRemoteCacheService<>();
+
+        ZombieCacheServiceNonLocal<String, String> zombie = new ZombieCacheServiceNonLocal<>( 10 );
+
+        String cacheName = "testRemoveThenWalk";
+
+        // DO WORK
+        zombie.removeAll( cacheName, 123l );
+        zombie.propagateEvents( service );
+
+        // VERIFY
+        assertEquals( "Updated element is not as expected.", cacheName, service.lastRemoveAllCacheName);
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/http/client/MockRemoteCacheDispatcher.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/http/client/MockRemoteCacheDispatcher.java
new file mode 100644
index 0000000..575bd33
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/http/client/MockRemoteCacheDispatcher.java
@@ -0,0 +1,53 @@
+package org.apache.commons.jcs3.auxiliary.remote.http.client;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheDispatcher;
+import org.apache.commons.jcs3.auxiliary.remote.value.RemoteCacheRequest;
+import org.apache.commons.jcs3.auxiliary.remote.value.RemoteCacheResponse;
+
+/** For testing the service. */
+public class MockRemoteCacheDispatcher
+    implements IRemoteCacheDispatcher
+{
+    /** The last request passes to dispatch */
+    public RemoteCacheRequest<?, ?> lastRemoteCacheRequest;
+
+    /** The response setup */
+    public RemoteCacheResponse<?> setupRemoteCacheResponse;
+
+    /** Records the last and returns setupRemoteCacheResponse.
+     * <p>
+     * @param remoteCacheRequest
+     * @return RemoteCacheResponse
+     * @throws IOException
+     */
+    @Override
+    @SuppressWarnings("unchecked")
+    public <K, V, T>
+        RemoteCacheResponse<T> dispatchRequest( RemoteCacheRequest<K, V> remoteCacheRequest )
+        throws IOException
+    {
+        this.lastRemoteCacheRequest = remoteCacheRequest;
+        return (RemoteCacheResponse<T>)setupRemoteCacheResponse;
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/http/client/RemoteHttpCacheClientUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/http/client/RemoteHttpCacheClientUnitTest.java
new file mode 100644
index 0000000..b9c0ce8
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/http/client/RemoteHttpCacheClientUnitTest.java
@@ -0,0 +1,278 @@
+package org.apache.commons.jcs3.auxiliary.remote.http.client;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.auxiliary.remote.http.client.RemoteHttpCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.remote.http.client.RemoteHttpCacheClient;
+import org.apache.commons.jcs3.auxiliary.remote.value.RemoteCacheResponse;
+import org.apache.commons.jcs3.auxiliary.remote.value.RemoteRequestType;
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+
+/** Unit tests for the client. */
+public class RemoteHttpCacheClientUnitTest
+    extends TestCase
+{
+    /**
+     * Verify get functionality
+     * <p>
+     * @throws IOException
+     */
+    public void testGet_nullFromDispatcher()
+        throws IOException
+    {
+        // SETUP
+        RemoteHttpCacheAttributes attributes = new RemoteHttpCacheAttributes();
+        RemoteHttpCacheClient<String, String> client = new RemoteHttpCacheClient<>( attributes );
+
+        MockRemoteCacheDispatcher mockDispatcher = new MockRemoteCacheDispatcher();
+        client.setRemoteDispatcher( mockDispatcher );
+
+        String cacheName = "test";
+        String key = "key";
+
+        mockDispatcher.setupRemoteCacheResponse = null;
+
+        // DO WORK
+        ICacheElement<String, String> result = client.get( cacheName, key );
+
+        // VERIFY
+        assertNull( "Wrong result.", result );
+        assertEquals( "Wrong type.", RemoteRequestType.GET, mockDispatcher.lastRemoteCacheRequest
+            .getRequestType() );
+    }
+
+    /**
+     * Verify get functionality
+     * <p>
+     * @throws IOException
+     */
+    public void testGet_normal()
+        throws IOException
+    {
+        // SETUP
+        RemoteHttpCacheAttributes attributes = new RemoteHttpCacheAttributes();
+        RemoteHttpCacheClient<String, String> client = new RemoteHttpCacheClient<>( attributes );
+
+        MockRemoteCacheDispatcher mockDispatcher = new MockRemoteCacheDispatcher();
+        client.setRemoteDispatcher( mockDispatcher );
+
+        String cacheName = "test";
+        String key = "key";
+
+        ICacheElement<String, String> expected = new CacheElement<>( cacheName, key, "value" );
+        RemoteCacheResponse<ICacheElement<String, String>> remoteHttpCacheResponse =
+            new RemoteCacheResponse<>();
+        remoteHttpCacheResponse.setPayload( expected );
+
+        mockDispatcher.setupRemoteCacheResponse = remoteHttpCacheResponse;
+
+        // DO WORK
+        ICacheElement<String, String> result = client.get( cacheName, key );
+
+        // VERIFY
+        assertEquals( "Wrong result.", expected, result );
+        assertEquals( "Wrong type.", RemoteRequestType.GET, mockDispatcher.lastRemoteCacheRequest
+            .getRequestType() );
+    }
+
+    /**
+     * Verify get functionality
+     * <p>
+     * @throws IOException
+     */
+    public void testGetMatching_normal()
+        throws IOException
+    {
+        // SETUP
+        RemoteHttpCacheAttributes attributes = new RemoteHttpCacheAttributes();
+        RemoteHttpCacheClient<String, String> client = new RemoteHttpCacheClient<>( attributes );
+
+        MockRemoteCacheDispatcher mockDispatcher = new MockRemoteCacheDispatcher();
+        client.setRemoteDispatcher( mockDispatcher );
+
+        String cacheName = "test";
+        String pattern = "key";
+
+        ICacheElement<String, String> expected = new CacheElement<>( cacheName, "key", "value" );
+        Map<String, ICacheElement<String, String>> expectedMap = new HashMap<>();
+        expectedMap.put( "key", expected );
+        RemoteCacheResponse<Map<String, ICacheElement<String, String>>> remoteHttpCacheResponse =
+            new RemoteCacheResponse<>();
+        remoteHttpCacheResponse.setPayload( expectedMap );
+
+        mockDispatcher.setupRemoteCacheResponse = remoteHttpCacheResponse;
+
+        // DO WORK
+        Map<String, ICacheElement<String, String>> result = client.getMatching( cacheName, pattern );
+
+        // VERIFY
+        assertEquals( "Wrong result.", expected, result.get( "key" ) );
+        assertEquals( "Wrong type.", RemoteRequestType.GET_MATCHING,
+                      mockDispatcher.lastRemoteCacheRequest.getRequestType() );
+    }
+
+    /**
+     * Verify get functionality
+     * <p>
+     * @throws IOException
+     */
+    public void testGetMultiple_normal()
+        throws IOException
+    {
+        // SETUP
+        RemoteHttpCacheAttributes attributes = new RemoteHttpCacheAttributes();
+        RemoteHttpCacheClient<String, String> client = new RemoteHttpCacheClient<>( attributes );
+
+        MockRemoteCacheDispatcher mockDispatcher = new MockRemoteCacheDispatcher();
+        client.setRemoteDispatcher( mockDispatcher );
+
+        String cacheName = "test";
+        Set<String> keys = Collections.emptySet();
+
+        ICacheElement<String, String> expected = new CacheElement<>( cacheName, "key", "value" );
+        Map<String, ICacheElement<String, String>> expectedMap = new HashMap<>();
+        expectedMap.put( "key", expected );
+        RemoteCacheResponse<Map<String, ICacheElement<String, String>>> remoteHttpCacheResponse =
+            new RemoteCacheResponse<>();
+        remoteHttpCacheResponse.setPayload( expectedMap );
+
+        mockDispatcher.setupRemoteCacheResponse = remoteHttpCacheResponse;
+
+        // DO WORK
+        Map<String, ICacheElement<String, String>> result = client.getMultiple( cacheName, keys );
+
+        // VERIFY
+        assertEquals( "Wrong result.", expected, result.get( "key" ) );
+        assertEquals( "Wrong type.", RemoteRequestType.GET_MULTIPLE,
+                      mockDispatcher.lastRemoteCacheRequest.getRequestType() );
+    }
+
+    /**
+     * Verify remove functionality
+     * <p>
+     * @throws IOException
+     */
+    public void testRemove_normal()
+        throws IOException
+    {
+        // SETUP
+        RemoteHttpCacheAttributes attributes = new RemoteHttpCacheAttributes();
+        RemoteHttpCacheClient<String, String> client = new RemoteHttpCacheClient<>( attributes );
+
+        MockRemoteCacheDispatcher mockDispatcher = new MockRemoteCacheDispatcher();
+        client.setRemoteDispatcher( mockDispatcher );
+
+        String cacheName = "test";
+        String key = "key";
+
+        // DO WORK
+        client.remove( cacheName, key );
+
+        // VERIFY
+        assertEquals( "Wrong type.", RemoteRequestType.REMOVE, mockDispatcher.lastRemoteCacheRequest
+            .getRequestType() );
+    }
+
+    /**
+     * Verify removeall functionality
+     * <p>
+     * @throws IOException
+     */
+    public void testRemoveAll_normal()
+        throws IOException
+    {
+        // SETUP
+        RemoteHttpCacheAttributes attributes = new RemoteHttpCacheAttributes();
+        RemoteHttpCacheClient<String, String> client = new RemoteHttpCacheClient<>( attributes );
+
+        MockRemoteCacheDispatcher mockDispatcher = new MockRemoteCacheDispatcher();
+        client.setRemoteDispatcher( mockDispatcher );
+
+        String cacheName = "test";
+
+        // DO WORK
+        client.removeAll( cacheName );
+
+        // VERIFY
+        assertEquals( "Wrong type.", RemoteRequestType.REMOVE_ALL, mockDispatcher.lastRemoteCacheRequest
+            .getRequestType() );
+    }
+
+    /**
+     * Verify update functionality
+     * <p>
+     * @throws IOException
+     */
+    public void testUpdate_normal()
+        throws IOException
+    {
+        // SETUP
+        RemoteHttpCacheAttributes attributes = new RemoteHttpCacheAttributes();
+        RemoteHttpCacheClient<String, String> client = new RemoteHttpCacheClient<>( attributes );
+
+        MockRemoteCacheDispatcher mockDispatcher = new MockRemoteCacheDispatcher();
+        client.setRemoteDispatcher( mockDispatcher );
+
+        String cacheName = "test";
+
+        ICacheElement<String, String> element = new CacheElement<>( cacheName, "key", "value" );
+
+        // DO WORK
+        client.update( element );
+
+        // VERIFY
+        assertEquals( "Wrong type.", RemoteRequestType.UPDATE, mockDispatcher.lastRemoteCacheRequest
+            .getRequestType() );
+    }
+
+    /**
+     * Verify dispose functionality
+     * <p>
+     * @throws IOException
+     */
+    public void testDispose_normal()
+        throws IOException
+    {
+        // SETUP
+        RemoteHttpCacheAttributes attributes = new RemoteHttpCacheAttributes();
+        RemoteHttpCacheClient<String, String> client = new RemoteHttpCacheClient<>( attributes );
+
+        MockRemoteCacheDispatcher mockDispatcher = new MockRemoteCacheDispatcher();
+        client.setRemoteDispatcher( mockDispatcher );
+
+        String cacheName = "test";
+
+        // DO WORK
+        client.dispose( cacheName );
+
+        // VERIFY
+        assertEquals( "Wrong type.", RemoteRequestType.DISPOSE, mockDispatcher.lastRemoteCacheRequest
+            .getRequestType() );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/http/client/RemoteHttpCacheFactoryUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/http/client/RemoteHttpCacheFactoryUnitTest.java
new file mode 100644
index 0000000..d2c5388
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/http/client/RemoteHttpCacheFactoryUnitTest.java
@@ -0,0 +1,93 @@
+package org.apache.commons.jcs3.auxiliary.remote.http.client;
+
+import org.apache.commons.jcs3.engine.control.MockCompositeCacheManager;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCache;
+import org.apache.commons.jcs3.auxiliary.remote.http.client.RemoteHttpCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.remote.http.client.RemoteHttpCacheClient;
+import org.apache.commons.jcs3.auxiliary.remote.http.client.RemoteHttpCacheFactory;
+import org.apache.commons.jcs3.auxiliary.remote.http.client.behavior.IRemoteHttpCacheClient;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/** Unit tests for the manager. */
+public class RemoteHttpCacheFactoryUnitTest
+    extends TestCase
+{
+    /** Verify that we get the default. */
+    public void testCreateRemoteHttpCacheClient_Bad()
+    {
+        // SETUP
+        String remoteHttpClientClassName = "junk";
+        RemoteHttpCacheAttributes cattr = new RemoteHttpCacheAttributes();
+        cattr.setRemoteHttpClientClassName( remoteHttpClientClassName );
+
+        RemoteHttpCacheFactory factory = new RemoteHttpCacheFactory();
+
+        // DO WORK
+        IRemoteHttpCacheClient<String, String> result = factory.createRemoteHttpCacheClientForAttributes( cattr );
+
+        // VEIFY
+        assertNotNull( "Should have a cache.", result );
+        assertTrue( "Wrong default.", result instanceof RemoteHttpCacheClient );
+        assertTrue( "Should be initialized", ((RemoteHttpCacheClient<String, String>)result).isInitialized() );
+    }
+
+    /** Verify that we get the default. */
+    public void testCreateRemoteHttpCacheClient_default()
+    {
+        // SETUP
+        RemoteHttpCacheAttributes cattr = new RemoteHttpCacheAttributes();
+        RemoteHttpCacheFactory factory = new RemoteHttpCacheFactory();
+
+        // DO WORK
+        IRemoteHttpCacheClient<String, String> result = factory.createRemoteHttpCacheClientForAttributes( cattr );
+
+        // VEIFY
+        assertNotNull( "Should have a cache.", result );
+        assertTrue( "Wrong default.", result instanceof RemoteHttpCacheClient );
+    }
+
+    /** Verify that we get a cache no wait. */
+    public void testGetCache_normal()
+    {
+        // SETUP
+        ICompositeCacheManager cacheMgr = new MockCompositeCacheManager();
+        assertNotNull( "Should have a manager.", cacheMgr );
+        ICacheEventLogger cacheEventLogger = null;
+        IElementSerializer elementSerializer = null;
+
+        RemoteHttpCacheAttributes cattr = new RemoteHttpCacheAttributes();
+        assertNotNull( "Should have attributes.", cattr );
+        RemoteHttpCacheFactory factory = new RemoteHttpCacheFactory();
+        assertNotNull( "Should have a factory.", factory );
+
+
+        // DO WORK
+        AuxiliaryCache<String, String> result = factory.createCache(cattr, cacheMgr, cacheEventLogger, elementSerializer);
+
+        // VERIFY
+        assertNotNull( "Should have a cache.", result );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/http/client/RemoteHttpCacheManualTester.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/http/client/RemoteHttpCacheManualTester.java
new file mode 100644
index 0000000..0c3cac2
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/http/client/RemoteHttpCacheManualTester.java
@@ -0,0 +1,76 @@
+package org.apache.commons.jcs3.auxiliary.remote.http.client;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/** Manual tester for a JCS instance configured to use the http client. */
+public class RemoteHttpCacheManualTester
+    extends TestCase
+{
+    /** number to use for the test */
+    private static int items = 100;
+
+    /**
+     * Test setup
+     */
+    @Override
+    public void setUp()
+    {
+        JCS.setConfigFilename( "/TestRemoteHttpCache.ccf" );
+    }
+
+    /**
+     * A unit test for JUnit
+     * @throws Exception Description of the Exception
+     */
+    public void testSimpleLoad()
+        throws Exception
+    {
+        CacheAccess<String, String> jcs = JCS.getInstance( "testCache1" );
+
+        jcs.put( "TestKey", "TestValue" );
+
+//        System.out.println( jcs.getStats() );
+
+        for ( int i = 1; i <= items; i++ )
+        {
+            jcs.put( i + ":key", "data" + i );
+        }
+
+        for ( int i = items; i > 0; i-- )
+        {
+            String res = jcs.get( i + ":key" );
+            if ( res == null )
+            {
+                //assertNotNull( "[" + i + ":key] should not be null", res );
+            }
+        }
+
+        // test removal
+        jcs.remove( "300:key" );
+        assertNull( jcs.get( "TestKey" ) );
+
+//        System.out.println( jcs.getStats() );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/http/server/RemoteHttpCacheServiceUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/http/server/RemoteHttpCacheServiceUnitTest.java
new file mode 100644
index 0000000..6922f56
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/http/server/RemoteHttpCacheServiceUnitTest.java
@@ -0,0 +1,183 @@
+package org.apache.commons.jcs3.auxiliary.remote.http.server;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+import org.apache.commons.jcs3.auxiliary.MockCacheEventLogger;
+import org.apache.commons.jcs3.engine.control.MockCompositeCacheManager;
+import org.apache.commons.jcs3.auxiliary.remote.http.server.RemoteHttpCacheServerAttributes;
+import org.apache.commons.jcs3.auxiliary.remote.http.server.RemoteHttpCacheService;
+import org.apache.commons.jcs3.engine.CacheElement;
+
+import java.util.HashSet;
+
+/** Unit tests for the service. */
+public class RemoteHttpCacheServiceUnitTest
+    extends TestCase
+{
+    /**
+     * Verify event log calls.
+     * <p>
+     * @throws Exception
+     */
+    public void testUpdate_simple()
+        throws Exception
+    {
+        // SETUP
+        MockCompositeCacheManager manager = new MockCompositeCacheManager();
+        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
+
+        RemoteHttpCacheServerAttributes rcsa = new RemoteHttpCacheServerAttributes();
+        RemoteHttpCacheService<String, String> server =
+            new RemoteHttpCacheService<>( manager, rcsa, cacheEventLogger );
+
+        String cacheName = "test";
+        String key = "key";
+        long requesterId = 2;
+        CacheElement<String, String> element = new CacheElement<>( cacheName, key, null );
+
+        // DO WORK
+        server.update( element, requesterId );
+
+        // VERIFY
+        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
+        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
+    }
+
+    /**
+     * Verify event log calls.
+     * <p>
+     * @throws Exception
+     */
+    public void testGet_simple()
+        throws Exception
+    {
+        // SETUP
+        MockCompositeCacheManager manager = new MockCompositeCacheManager();
+        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
+
+        RemoteHttpCacheServerAttributes rcsa = new RemoteHttpCacheServerAttributes();
+        RemoteHttpCacheService<String, String> server =
+            new RemoteHttpCacheService<>( manager, rcsa, cacheEventLogger );
+
+        // DO WORK
+        server.get( "region", "key" );
+
+        // VERIFY
+        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
+        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
+    }
+
+    /**
+     * Verify event log calls.
+     * <p>
+     * @throws Exception
+     */
+    public void testGetMatching_simple()
+        throws Exception
+    {
+        // SETUP
+        MockCompositeCacheManager manager = new MockCompositeCacheManager();
+        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
+
+        RemoteHttpCacheServerAttributes rcsa = new RemoteHttpCacheServerAttributes();
+        RemoteHttpCacheService<String, String> server =
+            new RemoteHttpCacheService<>( manager, rcsa, cacheEventLogger );
+
+        // DO WORK
+        server.getMatching( "region", "pattern", 0 );
+
+        // VERIFY
+        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
+        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
+    }
+
+    /**
+     * Verify event log calls.
+     * <p>
+     * @throws Exception
+     */
+    public void testGetMultiple_simple()
+        throws Exception
+    {
+        // SETUP
+        MockCompositeCacheManager manager = new MockCompositeCacheManager();
+        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
+
+        RemoteHttpCacheServerAttributes rcsa = new RemoteHttpCacheServerAttributes();
+        RemoteHttpCacheService<String, String> server =
+            new RemoteHttpCacheService<>( manager, rcsa, cacheEventLogger );
+
+        // DO WORK
+        server.getMultiple( "region", new HashSet<>() );
+
+        // VERIFY
+        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
+        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
+    }
+
+    /**
+     * Verify event log calls.
+     * <p>
+     * @throws Exception
+     */
+    public void testRemove_simple()
+        throws Exception
+    {
+        // SETUP
+        MockCompositeCacheManager manager = new MockCompositeCacheManager();
+        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
+
+        RemoteHttpCacheServerAttributes rcsa = new RemoteHttpCacheServerAttributes();
+        RemoteHttpCacheService<String, String> server =
+            new RemoteHttpCacheService<>( manager, rcsa, cacheEventLogger );
+
+        // DO WORK
+        server.remove( "region", "key" );
+
+        // VERIFY
+        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
+        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
+    }
+
+    /**
+     * Verify event log calls.
+     * <p>
+     * @throws Exception
+     */
+    public void testRemoveAll_simple()
+        throws Exception
+    {
+        // SETUP
+        MockCompositeCacheManager manager = new MockCompositeCacheManager();
+        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
+
+        RemoteHttpCacheServerAttributes rcsa = new RemoteHttpCacheServerAttributes();
+        RemoteHttpCacheService<String, String> server =
+            new RemoteHttpCacheService<>( manager, rcsa, cacheEventLogger );
+
+        // DO WORK
+        server.removeAll( "region" );
+
+        // VERIFY
+        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
+        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/http/server/RemoteHttpCacheServletUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/http/server/RemoteHttpCacheServletUnitTest.java
new file mode 100644
index 0000000..b865d69
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/http/server/RemoteHttpCacheServletUnitTest.java
@@ -0,0 +1,178 @@
+package org.apache.commons.jcs3.auxiliary.remote.http.server;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Set;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.jcs3.auxiliary.remote.MockRemoteCacheService;
+import org.apache.commons.jcs3.auxiliary.remote.http.server.RemoteHttpCacheServlet;
+import org.apache.commons.jcs3.auxiliary.remote.util.RemoteCacheRequestFactory;
+import org.apache.commons.jcs3.auxiliary.remote.value.RemoteCacheRequest;
+import org.apache.commons.jcs3.auxiliary.remote.value.RemoteCacheResponse;
+import org.apache.commons.jcs3.engine.CacheElement;
+
+/** Unit tests for the servlet. */
+public class RemoteHttpCacheServletUnitTest
+    extends TestCase
+{
+    private RemoteHttpCacheServlet servlet;
+    private MockRemoteCacheService<Serializable, Serializable> remoteHttpCacheService;
+
+    /**
+     * @see junit.framework.TestCase#setUp()
+     */
+    @Override
+    protected void setUp() throws Exception
+    {
+        super.setUp();
+        servlet = new RemoteHttpCacheServlet();
+        servlet.init(null);
+
+        remoteHttpCacheService = new MockRemoteCacheService<>();
+        servlet.setRemoteCacheService( remoteHttpCacheService );
+    }
+
+    /**
+     * @see junit.framework.TestCase#tearDown()
+     */
+    @Override
+    protected void tearDown() throws Exception
+    {
+        servlet.destroy();
+        super.tearDown();
+    }
+
+    /** Verify that we balk and return an error. */
+    public void testProcessRequest_null()
+    {
+        RemoteCacheRequest<Serializable, Serializable> request = null;
+
+        // DO WORK
+        RemoteCacheResponse<Object> result = servlet.processRequest( request );
+
+        // VERIFY
+        assertNotNull( "Should have a result.", result );
+        assertTrue( "Should have 'The request is null' in the errorMessage", result.getErrorMessage().indexOf( "The request is null" ) != -1 );
+        assertTrue( "Should have 'The request is null' in the toString", result.toString().indexOf( "The request is null" ) != -1 );
+    }
+
+    /** Verify that the service is called. */
+    public void testProcessRequest_Get()
+    {
+        String cacheName = "test";
+        Serializable key = "key";
+        long requesterId = 2;
+        RemoteCacheRequest<Serializable, Serializable> request = RemoteCacheRequestFactory.createGetRequest( cacheName, key, requesterId );
+
+        // DO WORK
+        RemoteCacheResponse<Object> result = servlet.processRequest( request );
+
+        // VERIFY
+        assertNotNull( "Should have a result.", result );
+        assertEquals( "Wrong key.", key, remoteHttpCacheService.lastGetKey );
+    }
+
+    /** Verify that the service is called. */
+    public void testProcessRequest_GetMatching()
+    {
+        String cacheName = "test";
+        String pattern = "pattern";
+        long requesterId = 2;
+        RemoteCacheRequest<Serializable, Serializable> request = RemoteCacheRequestFactory.createGetMatchingRequest( cacheName, pattern,
+                                                                                                  requesterId );
+
+        // DO WORK
+        RemoteCacheResponse<Object> result = servlet.processRequest( request );
+
+        // VERIFY
+        assertNotNull( "Should have a result.", result );
+        assertEquals( "Wrong pattern.", pattern, remoteHttpCacheService.lastGetMatchingPattern );
+    }
+
+    /** Verify that the service is called. */
+    public void testProcessRequest_GetMultiple()
+    {
+        String cacheName = "test";
+        Set<Serializable> keys = Collections.emptySet();
+        long requesterId = 2;
+        RemoteCacheRequest<Serializable, Serializable> request = RemoteCacheRequestFactory.createGetMultipleRequest( cacheName, keys,
+                                                                                                  requesterId );
+
+        // DO WORK
+        RemoteCacheResponse<Object> result = servlet.processRequest( request );
+
+        // VERIFY
+        assertNotNull( "Should have a result.", result );
+        assertEquals( "Wrong keys.", keys, remoteHttpCacheService.lastGetMultipleKeys );
+
+    }
+
+    /** Verify that the service is called. */
+    public void testProcessRequest_Update()
+    {
+        String cacheName = "test";
+        String key = "key";
+        long requesterId = 2;
+        CacheElement<Serializable, Serializable> element = new CacheElement<>( cacheName, key, null );
+        RemoteCacheRequest<Serializable, Serializable> request = RemoteCacheRequestFactory.createUpdateRequest( element, requesterId );
+
+        // DO WORK
+        RemoteCacheResponse<Object> result = servlet.processRequest( request );
+
+        // VERIFY
+        assertNotNull( "Should have a result.", result );
+        assertEquals( "Wrong object.", element, remoteHttpCacheService.lastUpdate );
+    }
+
+    /** Verify that the service is called. */
+    public void testProcessRequest_Remove()
+    {
+        String cacheName = "test";
+        Serializable key = "key";
+        long requesterId = 2;
+        RemoteCacheRequest<Serializable, Serializable> request = RemoteCacheRequestFactory.createRemoveRequest( cacheName, key, requesterId );
+
+        // DO WORK
+        RemoteCacheResponse<Object> result = servlet.processRequest( request );
+
+        // VERIFY
+        assertNotNull( "Should have a result.", result );
+        assertEquals( "Wrong key.", key, remoteHttpCacheService.lastRemoveKey );
+    }
+
+    /** Verify that the service is called. */
+    public void testProcessRequest_RemoveAll()
+    {
+        String cacheName = "testRemoveALl";
+        long requesterId = 2;
+        RemoteCacheRequest<Serializable, Serializable> request = RemoteCacheRequestFactory.createRemoveAllRequest( cacheName, requesterId );
+
+        // DO WORK
+        RemoteCacheResponse<Object> result = servlet.processRequest( request );
+
+        // VERIFY
+        assertNotNull( "Should have a result.", result );
+        assertEquals( "Wrong cacheName.", cacheName, remoteHttpCacheService.lastRemoveAllCacheName );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/server/BasicRemoteCacheClientServerUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/server/BasicRemoteCacheClientServerUnitTest.java
new file mode 100644
index 0000000..cb04b7c
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/server/BasicRemoteCacheClientServerUnitTest.java
@@ -0,0 +1,342 @@
+package org.apache.commons.jcs3.auxiliary.remote.server;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.util.Enumeration;
+
+import org.apache.commons.jcs3.auxiliary.MockCacheEventLogger;
+import org.apache.commons.jcs3.auxiliary.remote.MockRemoteCacheListener;
+import org.apache.commons.jcs3.engine.control.MockCompositeCacheManager;
+import org.apache.commons.jcs3.engine.control.MockElementSerializer;
+import org.apache.commons.jcs3.utils.timing.SleepUtil;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCache;
+import org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory;
+import org.apache.commons.jcs3.auxiliary.remote.RemoteCacheManager;
+import org.apache.commons.jcs3.auxiliary.remote.server.RemoteCacheServer;
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.CacheStatus;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.utils.net.HostNameUtil;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * These tests startup the remote server and make requests to it.
+ * <p>
+ *
+ * @author Aaron Smuts
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class BasicRemoteCacheClientServerUnitTest extends Assert
+{
+    private static final int LOCAL_PORT = 12020;
+
+   /**
+     * Server instance to use in the tests.
+     */
+    private static RemoteCacheServer<String, String> server = null;
+
+    /**
+     * Factory instance to use in the tests.
+     */
+    private static RemoteCacheFactory factory = null;
+
+    /**
+     * the remote server port
+     */
+    private static int remotePort;
+
+    /**
+     * Starts the server. This is not in a setup, since the server is slow to kill right now.
+     */
+    @BeforeClass
+    public static void setup()
+    {
+        // Add some debug to try and find out why test fails on Jenkins/Continuum
+        try {
+            InetAddress lh = InetAddress.getByName("localhost");
+            System.out.println("localhost="+lh);
+            InetAddress ina=InetAddress.getLocalHost();
+            System.out.println("InetAddress.getLocalHost()="+ina);
+            // Iterate all NICs (network interface cards)...
+            for ( Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces(); ifaces.hasMoreElements(); )
+            {
+                NetworkInterface iface = ifaces.nextElement();
+                // Iterate all IP addresses assigned to each card...
+                for ( Enumeration<InetAddress> inetAddrs = iface.getInetAddresses(); inetAddrs.hasMoreElements(); )
+                {
+                    InetAddress inetAddr = inetAddrs.nextElement();
+                    boolean loopbackAddress = inetAddr.isLoopbackAddress();
+                    boolean siteLocalAddress = inetAddr.isSiteLocalAddress();
+                    System.out.println("Found: "+ inetAddr +
+                            " isLoopback: " + loopbackAddress +
+                            " isSiteLocal: " + siteLocalAddress +
+                            ((!loopbackAddress && siteLocalAddress) ? " *" : ""));
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        // end of debug
+        String configFile = "TestRemoteCacheClientServer.ccf";
+        server = RemoteCacheServerStartupUtil.startServerUsingProperties(configFile);
+        factory = new RemoteCacheFactory();
+        factory.initialize();
+        remotePort = server.remoteCacheServerAttributes.getRemoteLocation().getPort();
+    }
+
+    @AfterClass
+    public static void stop() throws IOException
+    {
+        if (server != null) { // in case setup failed, no point throwing NPE as well
+            server.shutdown("localhost", remotePort);
+        }
+        // Debug: unfortunately Surefire restarts JVM so log files get overwritten
+        // There's probably a better way to fix this ...
+        java.io.File jcsLog = new java.io.File("target/jcs.log");
+        java.io.File logSave = new java.io.File("target/BasicRemoteCacheClientServerUnitTest_jcs.log");
+        System.out.println("Renamed log file? "+jcsLog.renameTo(logSave));
+    }
+
+    /**
+     * Verify that we can start the remote cache server. Send an item to the remote. Verify that the
+     * remote put count goes up. If we go through JCS, the manager will be shared and we will get
+     * into an endless loop. We will use a mock cache manager instead.
+     * <p>
+     * The remote server uses the real JCS. We can verify that items are added to JCS behind the
+     * server by calling get. We cannot access it directly via JCS since it is serialized.
+     * <p>
+     * This test uses a mock injected client to test a normal server.
+     * <p>
+     *
+     * @throws Exception
+     */
+    @Test
+    public void test1SinglePut()
+            throws Exception
+            {
+        // SETUP
+        MockCompositeCacheManager compositeCacheManager = new MockCompositeCacheManager();
+
+        RemoteCacheAttributes attributes = new RemoteCacheAttributes();
+        attributes.setRemoteLocation("localhost", remotePort);
+        attributes.setLocalPort(LOCAL_PORT);
+        attributes.setCacheName("testSinglePut");
+
+        RemoteCacheManager remoteCacheManager = factory.getManager(attributes, compositeCacheManager, new MockCacheEventLogger(), new MockElementSerializer());
+        AuxiliaryCache<String, String> cache = remoteCacheManager.getCache(attributes);
+
+        // DO WORK
+        int numPutsPrior = server.getPutCount();
+        ICacheElement<String, String> element = new CacheElement<>(cache.getCacheName(), "key", "value");
+        cache.update(element);
+        SleepUtil.sleepAtLeast(200);
+
+        // VERIFY
+        try
+        {
+            assertEquals("Cache is alive", CacheStatus.ALIVE, cache.getStatus());
+            assertEquals("Wrong number of puts", 1, server.getPutCount() - numPutsPrior);
+        }
+        catch (junit.framework.AssertionFailedError e)
+        {
+            System.out.println(cache.getStats());
+            System.out.println(server.getStats());
+            throw e;
+        }
+
+        // DO WORK
+        ICacheElement<String, String> result = cache.get("key");
+
+        // VERIFY
+        assertEquals("Wrong element.", element.getVal(), result.getVal());
+            }
+
+    /**
+     * Verify that we can remove an item via the remote server.
+     * <p>
+     *
+     * @throws Exception
+     */
+    @Test
+    public void test2PutRemove()
+            throws Exception
+            {
+        // SETUP
+        MockCompositeCacheManager compositeCacheManager = new MockCompositeCacheManager();
+
+        RemoteCacheAttributes attributes = new RemoteCacheAttributes();
+        attributes.setRemoteLocation("localhost", remotePort);
+        attributes.setLocalPort(LOCAL_PORT);
+        attributes.setCacheName("testPutRemove");
+
+        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
+
+        RemoteCacheManager remoteCacheManager = factory.getManager(attributes, compositeCacheManager, cacheEventLogger, null);
+        AuxiliaryCache<String, String> cache = remoteCacheManager.getCache(attributes);
+
+        // DO WORK
+        int numPutsPrior = server.getPutCount();
+        ICacheElement<String, String> element = new CacheElement<>(cache.getCacheName(), "key", "value");
+        cache.update(element);
+        SleepUtil.sleepAtLeast(50);
+
+        // VERIFY
+        try
+        {
+            assertEquals("Cache is alive", CacheStatus.ALIVE, cache.getStatus());
+            assertEquals("Wrong number of puts", 1, server.getPutCount() - numPutsPrior);
+        }
+        catch (junit.framework.AssertionFailedError e)
+        {
+            System.out.println(cache.getStats());
+            System.out.println(server.getStats());
+            throw e;
+        }
+
+        // DO WORK
+        ICacheElement<String, String> result = cache.get("key");
+
+        // VERIFY
+        assertEquals("Wrong element.", element.getVal(), result.getVal());
+
+        // DO WORK
+        cache.remove("key");
+        SleepUtil.sleepAtLeast(200);
+        ICacheElement<String, String> resultAfterRemote = cache.get("key");
+
+        // VERIFY
+        assertNull("Element resultAfterRemote should be null.", resultAfterRemote);
+            }
+
+    /**
+     * Register a listener with the server. Send an update. Verify that the listener received it.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void test3PutAndListen()
+            throws Exception
+            {
+        // SETUP
+        MockCompositeCacheManager compositeCacheManager = new MockCompositeCacheManager();
+
+        RemoteCacheAttributes attributes = new RemoteCacheAttributes();
+        attributes.setRemoteLocation("localhost", remotePort);
+        attributes.setLocalPort(LOCAL_PORT);
+        attributes.setCacheName("testPutAndListen");
+
+        RemoteCacheManager remoteCacheManager = factory.getManager(attributes, compositeCacheManager, new MockCacheEventLogger(), new MockElementSerializer());
+        AuxiliaryCache<String, String> cache = remoteCacheManager.getCache(attributes);
+
+        MockRemoteCacheListener<String, String> listener = new MockRemoteCacheListener<>();
+        server.addCacheListener(cache.getCacheName(), listener);
+
+        // DO WORK
+        int numPutsPrior = server.getPutCount();
+        ICacheElement<String, String> element = new CacheElement<>(cache.getCacheName(), "key", "value");
+        cache.update(element);
+        SleepUtil.sleepAtLeast(50);
+
+        // VERIFY
+        try
+        {
+            assertEquals("Cache is alive", CacheStatus.ALIVE, cache.getStatus());
+            assertEquals("Wrong number of puts", 1, server.getPutCount() - numPutsPrior);
+            assertEquals("Wrong number of puts to listener.", 1, listener.putCount);
+        }
+        catch (junit.framework.AssertionFailedError e)
+        {
+            System.out.println(cache.getStats());
+            System.out.println(server.getStats());
+            throw e;
+        }
+        finally
+        {
+            // remove from all regions.
+            server.removeCacheListener(listener);
+        }
+    }
+
+    /**
+     * Register a listener with the server. Send an update. Verify that the listener received it.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void test4PutaMultipleAndListen()
+            throws Exception
+    {
+        // SETUP
+        MockCompositeCacheManager compositeCacheManager = new MockCompositeCacheManager();
+
+        RemoteCacheAttributes attributes = new RemoteCacheAttributes();
+        attributes.setRemoteLocation("localhost", remotePort);
+        attributes.setLocalPort(LOCAL_PORT);
+        attributes.setCacheName("testPutaMultipleAndListen");
+
+        RemoteCacheManager remoteCacheManager = factory.getManager(attributes, compositeCacheManager, new MockCacheEventLogger(), new MockElementSerializer());
+        AuxiliaryCache<String, String> cache = remoteCacheManager.getCache(attributes);
+
+        MockRemoteCacheListener<String, String> listener = new MockRemoteCacheListener<>();
+        server.addCacheListener(cache.getCacheName(), listener);
+
+        // DO WORK
+        int numPutsPrior = server.getPutCount();
+        int numToPut = 100;
+        for (int i = 0; i < numToPut; i++)
+        {
+            ICacheElement<String, String> element = new CacheElement<>(cache.getCacheName(), "key" + 1, "value" + i);
+            cache.update(element);
+        }
+        SleepUtil.sleepAtLeast(500);
+
+        // VERIFY
+        try
+        {
+            assertEquals("Cache is alive", CacheStatus.ALIVE, cache.getStatus());
+            assertEquals("Wrong number of puts", numToPut, server.getPutCount() - numPutsPrior);
+            assertEquals("Wrong number of puts to listener.", numToPut, listener.putCount);
+        }
+        catch (junit.framework.AssertionFailedError e)
+        {
+            System.out.println(cache.getStats());
+            System.out.println(server.getStats());
+            throw e;
+        }
+    }
+
+    @Test
+    public void testLocalHost() throws Exception
+    {
+        final InetAddress byName = InetAddress.getByName("localhost");
+        assertTrue("Expected localhost (" + byName.getHostAddress() + ") to be a loopback address", byName.isLoopbackAddress());
+        final InetAddress localHost = HostNameUtil.getLocalHostLANAddress();
+        assertTrue("Expected getLocalHostLANAddress() (" + localHost + ") to return a site local address", localHost.isSiteLocalAddress());
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/server/MockRMISocketFactory.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/server/MockRMISocketFactory.java
new file mode 100644
index 0000000..e345cd4
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/server/MockRMISocketFactory.java
@@ -0,0 +1,88 @@
+package org.apache.commons.jcs3.auxiliary.remote.server;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.rmi.server.RMISocketFactory;
+
+/** For testing the custom socket factory configuration */
+public class MockRMISocketFactory
+    extends RMISocketFactory
+    implements Serializable
+{
+    /** Don't change */
+    private static final long serialVersionUID = 1056199478581218676L;
+
+    /** for testing automatic property configuration. */
+    private String testStringProperty;
+
+    /**
+     * @param host
+     * @param port
+     * @return Socket
+     * @throws IOException
+     */
+    @Override
+    public Socket createSocket( String host, int port )
+        throws IOException
+    {
+//        System.out.println( "Creating socket" );
+
+        Socket socket = new Socket();
+        socket.setSoTimeout( 1000 );
+        socket.setSoLinger( false, 0 );
+        socket.connect( new InetSocketAddress( host, port ), 1000 );
+        return socket;
+    }
+
+    /**
+     * @param port
+     * @return ServerSocket
+     * @throws IOException
+     */
+    @Override
+    public ServerSocket createServerSocket( int port )
+        throws IOException
+    {
+//        System.out.println( "Creating server socket" );
+
+        return new ServerSocket( port );
+    }
+
+    /**
+     * @param testStringProperty the testStringProperty to set
+     */
+    public void setTestStringProperty( String testStringProperty )
+    {
+        this.testStringProperty = testStringProperty;
+    }
+
+    /**
+     * @return the testStringProperty
+     */
+    public String getTestStringProperty()
+    {
+        return testStringProperty;
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/server/RegistryKeepAliveRunnerUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/server/RegistryKeepAliveRunnerUnitTest.java
new file mode 100644
index 0000000..4b6aec1
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/server/RegistryKeepAliveRunnerUnitTest.java
@@ -0,0 +1,50 @@
+package org.apache.commons.jcs3.auxiliary.remote.server;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+import org.apache.commons.jcs3.auxiliary.MockCacheEventLogger;
+import org.apache.commons.jcs3.auxiliary.remote.server.RegistryKeepAliveRunner;
+
+/** Unit tests for the registry keep alive runner. */
+public class RegistryKeepAliveRunnerUnitTest
+    extends TestCase
+{
+    /** Verify that we get the appropriate event log */
+    public void testCheckAndRestoreIfNeeded_failure()
+    {
+        // SETUP
+        String host = "localhost";
+        int port = 1234;
+        String service = "doesn'texist";
+        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
+
+        RegistryKeepAliveRunner runner = new RegistryKeepAliveRunner( host, port, service );
+        runner.setCacheEventLogger( cacheEventLogger );
+
+        // DO WORK
+        runner.checkAndRestoreIfNeeded();
+
+        // VERIFY
+        // 1 for the lookup, one for the rebind since the server isn't created yet
+        assertEquals( "error tally", 2, cacheEventLogger.errorEventCalls );
+        //System.out.println( cacheEventLogger.errorMessages );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/server/RemoteCacheServerAttributesUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/server/RemoteCacheServerAttributesUnitTest.java
new file mode 100644
index 0000000..1e2569b
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/server/RemoteCacheServerAttributesUnitTest.java
@@ -0,0 +1,66 @@
+package org.apache.commons.jcs3.auxiliary.remote.server;
+
+import org.apache.commons.jcs3.auxiliary.remote.server.RemoteCacheServerAttributes;
+import org.apache.commons.jcs3.auxiliary.remote.server.behavior.RemoteType;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for the remote cache server attributes.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class RemoteCacheServerAttributesUnitTest
+    extends TestCase
+{
+
+    /**
+     * Verify that we get a string, even if not attributes are set.
+     */
+    public void testToString()
+    {
+        RemoteCacheServerAttributes attributes = new RemoteCacheServerAttributes();
+        assertNotNull( "Should have a string.", attributes.toString() );
+    }
+
+    /**
+     * Verify that the type is set correctly and that the correct name is returned for the type.
+     */
+    public void testSetRemoteTypeName_local()
+    {
+        RemoteCacheServerAttributes attributes = new RemoteCacheServerAttributes();
+        attributes.setRemoteTypeName( "LOCAL" );
+        assertEquals( "Wrong type.", RemoteType.LOCAL, attributes.getRemoteType() );
+        assertEquals( "Wrong name", "LOCAL", attributes.getRemoteTypeName() );
+    }
+
+    /**
+     * Verify that the type is set correctly and that the correct name is returned for the type.
+     */
+    public void testSetRemoteTypeName_cluster()
+    {
+        RemoteCacheServerAttributes attributes = new RemoteCacheServerAttributes();
+        attributes.setRemoteTypeName( "CLUSTER" );
+        assertEquals( "Wrong type.", RemoteType.CLUSTER, attributes.getRemoteType() );
+        assertEquals( "Wrong name", "CLUSTER", attributes.getRemoteTypeName() );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/server/RemoteCacheServerFactoryUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/server/RemoteCacheServerFactoryUnitTest.java
new file mode 100644
index 0000000..a5a4a0e
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/server/RemoteCacheServerFactoryUnitTest.java
@@ -0,0 +1,206 @@
+package org.apache.commons.jcs3.auxiliary.remote.server;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+import java.rmi.server.RMISocketFactory;
+import java.util.Properties;
+
+import org.apache.commons.jcs3.auxiliary.remote.behavior.ICommonRemoteCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheConstants;
+import org.apache.commons.jcs3.auxiliary.remote.server.RemoteCacheServerAttributes;
+import org.apache.commons.jcs3.auxiliary.remote.server.RemoteCacheServerFactory;
+import org.apache.commons.jcs3.auxiliary.remote.server.TimeoutConfigurableRMISocketFactory;
+
+/** Unit tests for the factory */
+public class RemoteCacheServerFactoryUnitTest
+    extends TestCase
+{
+    /** verify that we get the timeout value */
+    public void testConfigureRemoteCacheServerAttributes_eventQueuePoolName()
+    {
+        // SETUP
+        String eventQueuePoolName = "specialName";
+        Properties props = new Properties();
+        props.put( IRemoteCacheConstants.CACHE_SERVER_ATTRIBUTES_PROPERTY_PREFIX + ".EventQueuePoolName", eventQueuePoolName );
+
+        // DO WORK
+        RemoteCacheServerAttributes result = RemoteCacheServerFactory.configureRemoteCacheServerAttributes( props );
+
+        // VERIFY
+        assertEquals( "Wrong eventQueuePoolName", eventQueuePoolName, result.getEventQueuePoolName() );
+    }
+
+    /** verify that we get the timeout value */
+    public void testConfigureRemoteCacheServerAttributes_timeoutPresent()
+    {
+        // SETUP
+        int timeout = 123245;
+        Properties props = new Properties();
+        props.put( IRemoteCacheConstants.SOCKET_TIMEOUT_MILLIS, String.valueOf( timeout ) );
+
+        // DO WORK
+        RemoteCacheServerAttributes result = RemoteCacheServerFactory.configureRemoteCacheServerAttributes( props );
+
+        // VERIFY
+        assertEquals( "Wrong timeout", timeout, result.getRmiSocketFactoryTimeoutMillis() );
+    }
+
+    /** verify that we get the timeout value */
+    public void testConfigureRemoteCacheServerAttributes_timeoutNotPresent()
+    {
+        // SETUP
+        Properties props = new Properties();
+
+        // DO WORK
+        RemoteCacheServerAttributes result = RemoteCacheServerFactory.configureRemoteCacheServerAttributes( props );
+
+        // VERIFY
+        assertEquals( "Wrong timeout", ICommonRemoteCacheAttributes.DEFAULT_RMI_SOCKET_FACTORY_TIMEOUT_MILLIS, result.getRmiSocketFactoryTimeoutMillis() );
+    }
+
+    /** verify that we get the registryKeepAliveDelayMillis value */
+    public void testConfigureRemoteCacheServerAttributes_registryKeepAliveDelayMillisPresent()
+    {
+        // SETUP
+        int registryKeepAliveDelayMillis = 123245;
+        Properties props = new Properties();
+        props.put( IRemoteCacheConstants.CACHE_SERVER_ATTRIBUTES_PROPERTY_PREFIX + ".registryKeepAliveDelayMillis", String.valueOf( registryKeepAliveDelayMillis ) );
+
+        // DO WORK
+        RemoteCacheServerAttributes result = RemoteCacheServerFactory.configureRemoteCacheServerAttributes( props );
+
+        // VERIFY
+        assertEquals( "Wrong registryKeepAliveDelayMillis", registryKeepAliveDelayMillis, result.getRegistryKeepAliveDelayMillis() );
+    }
+
+    /** verify that we get the useRegistryKeepAlive value */
+    public void testConfigureRemoteCacheServerAttributes_useRegistryKeepAlivePresent()
+    {
+        // SETUP
+        boolean useRegistryKeepAlive = false;
+        Properties props = new Properties();
+        props.put( IRemoteCacheConstants.CACHE_SERVER_ATTRIBUTES_PROPERTY_PREFIX + ".useRegistryKeepAlive", String.valueOf( useRegistryKeepAlive ) );
+
+        // DO WORK
+        RemoteCacheServerAttributes result = RemoteCacheServerFactory.configureRemoteCacheServerAttributes( props );
+
+        // VERIFY
+        assertEquals( "Wrong useRegistryKeepAlive", useRegistryKeepAlive, result.isUseRegistryKeepAlive() );
+    }
+
+    /** verify that we get the startRegistry value */
+    public void testConfigureRemoteCacheServerAttributes_startRegistryPresent()
+    {
+        // SETUP
+        boolean startRegistry = false;
+        Properties props = new Properties();
+        props.put( IRemoteCacheConstants.CACHE_SERVER_ATTRIBUTES_PROPERTY_PREFIX + ".startRegistry", String.valueOf( startRegistry ) );
+
+        // DO WORK
+        RemoteCacheServerAttributes result = RemoteCacheServerFactory.configureRemoteCacheServerAttributes( props );
+
+        // VERIFY
+        assertEquals( "Wrong startRegistry", startRegistry, result.isStartRegistry() );
+    }
+
+    /** verify that we get the registryKeepAliveDelayMillis value */
+    public void testConfigureRemoteCacheServerAttributes_rmiSocketFactoryTimeoutMillisPresent()
+    {
+        // SETUP
+        int rmiSocketFactoryTimeoutMillis = 123245;
+        Properties props = new Properties();
+        props.put( IRemoteCacheConstants.CACHE_SERVER_ATTRIBUTES_PROPERTY_PREFIX + ".rmiSocketFactoryTimeoutMillis", String.valueOf( rmiSocketFactoryTimeoutMillis ) );
+
+        // DO WORK
+        RemoteCacheServerAttributes result = RemoteCacheServerFactory.configureRemoteCacheServerAttributes( props );
+
+        // VERIFY
+        assertEquals( "Wrong rmiSocketFactoryTimeoutMillis", rmiSocketFactoryTimeoutMillis, result.getRmiSocketFactoryTimeoutMillis() );
+    }
+
+    /** verify that we get the startRegistry value */
+    public void testConfigureRemoteCacheServerAttributes_allowClusterGetPresent()
+    {
+        // SETUP
+        boolean allowClusterGet = false;
+        Properties props = new Properties();
+        props.put( IRemoteCacheConstants.CACHE_SERVER_ATTRIBUTES_PROPERTY_PREFIX + ".allowClusterGet", String.valueOf( allowClusterGet ) );
+
+        // DO WORK
+        RemoteCacheServerAttributes result = RemoteCacheServerFactory.configureRemoteCacheServerAttributes( props );
+
+        // VERIFY
+        assertEquals( "Wrong allowClusterGet", allowClusterGet, result.isAllowClusterGet() );
+    }
+
+    /** verify that we get the startRegistry value */
+    public void testConfigureRemoteCacheServerAttributes_localClusterConsistencyPresent()
+    {
+        // SETUP
+        boolean localClusterConsistency = false;
+        Properties props = new Properties();
+        props.put( IRemoteCacheConstants.CACHE_SERVER_ATTRIBUTES_PROPERTY_PREFIX + ".localClusterConsistency", String.valueOf( localClusterConsistency ) );
+
+        // DO WORK
+        RemoteCacheServerAttributes result = RemoteCacheServerFactory.configureRemoteCacheServerAttributes( props );
+
+        // VERIFY
+        assertEquals( "Wrong localClusterConsistency", localClusterConsistency, result.isLocalClusterConsistency() );
+    }
+
+    /** verify that we get the timeout value */
+    public void testConfigureObjectSpecificCustomFactory_withProperty()
+    {
+        // SETUP
+        String testValue = "123245";
+        Properties props = new Properties();
+        props.put( IRemoteCacheConstants.CUSTOM_RMI_SOCKET_FACTORY_PROPERTY_PREFIX, MockRMISocketFactory.class.getName() );
+        props.put( IRemoteCacheConstants.CUSTOM_RMI_SOCKET_FACTORY_PROPERTY_PREFIX + ".testStringProperty", testValue );
+
+        // DO WORK
+        RMISocketFactory result = RemoteCacheServerFactory.configureObjectSpecificCustomFactory( props );
+
+        // VERIFY
+        assertNotNull( "Should have a custom socket factory.", result );
+        assertEquals( "Wrong testValue", testValue, ((MockRMISocketFactory)result).getTestStringProperty() );
+    }
+
+    /** verify that we get the timeout value */
+    public void testConfigureObjectSpecificCustomFactory_withProperty_TimeoutConfigurableRMIScoketFactory()
+    {
+        // SETUP
+        int readTimeout = 1234;
+        int openTimeout = 1234;
+        Properties props = new Properties();
+        props.put( IRemoteCacheConstants.CUSTOM_RMI_SOCKET_FACTORY_PROPERTY_PREFIX, TimeoutConfigurableRMISocketFactory.class.getName() );
+        props.put( IRemoteCacheConstants.CUSTOM_RMI_SOCKET_FACTORY_PROPERTY_PREFIX + ".readTimeout", String.valueOf( readTimeout ) );
+        props.put( IRemoteCacheConstants.CUSTOM_RMI_SOCKET_FACTORY_PROPERTY_PREFIX + ".openTimeout", String.valueOf( openTimeout ) );
+
+        // DO WORK
+        RMISocketFactory result = RemoteCacheServerFactory.configureObjectSpecificCustomFactory( props );
+
+        // VERIFY
+        assertNotNull( "Should have a custom socket factory.", result );
+        assertEquals( "Wrong readTimeout", readTimeout, ((TimeoutConfigurableRMISocketFactory)result).getReadTimeout() );
+        assertEquals( "Wrong readTimeout", openTimeout, ((TimeoutConfigurableRMISocketFactory)result).getOpenTimeout() );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/server/RemoteCacheServerStartupUtil.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/server/RemoteCacheServerStartupUtil.java
new file mode 100644
index 0000000..ba42cb1
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/server/RemoteCacheServerStartupUtil.java
@@ -0,0 +1,114 @@
+package org.apache.commons.jcs3.auxiliary.remote.server;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.net.UnknownHostException;
+import java.util.Properties;
+
+import org.apache.commons.jcs3.auxiliary.remote.RemoteUtils;
+import org.apache.commons.jcs3.auxiliary.remote.server.RemoteCacheServer;
+import org.apache.commons.jcs3.auxiliary.remote.server.RemoteCacheServerFactory;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.net.HostNameUtil;
+
+/**
+ *Starts the registry and runs the server via the factory.
+ *<p>
+ * @author Aaron Smuts
+ */
+public class RemoteCacheServerStartupUtil
+{
+    /** The logger */
+    private static final Log log = LogManager.getLog( RemoteCacheServerStartupUtil.class );
+
+    /** Registry to use in the test. */
+    private static final int DEFAULT_REGISTRY_PORT = 1101;
+
+    /**
+     * Starts the registry on port "registry.port"
+     * <p>
+     * @param propsFileName
+     * @return RemoteCacheServer
+     */
+    public static <K, V> RemoteCacheServer<K, V> startServerUsingProperties( String propsFileName )
+    {
+        // TODO load from props file or get as init param or get from jndi, or
+        // all three
+        int registryPort = DEFAULT_REGISTRY_PORT;
+
+        Properties props = null;
+        try
+        {
+            props = RemoteUtils.loadProps(propsFileName);
+        }
+        catch (IOException e)
+        {
+            log.error( "Problem loading configuration from " + propsFileName, e);
+        }
+
+        if ( props != null )
+        {
+            String portS = props.getProperty( "registry.port", String.valueOf( DEFAULT_REGISTRY_PORT ) );
+
+            try
+            {
+                registryPort = Integer.parseInt( portS );
+            }
+            catch ( NumberFormatException e )
+            {
+                log.error( "Problem converting port to an int.", e );
+            }
+        }
+
+        // we will always use the local machine for the registry
+        try
+        {
+            String registryHost = HostNameUtil.getLocalHostAddress();
+
+            if ( log.isDebugEnabled() )
+            {
+                log.debug( "registryHost = [" + registryHost + "]" );
+            }
+
+            if ( "localhost".equals( registryHost ) || "127.0.0.1".equals( registryHost ) )
+            {
+                log.warn( "The local address [" + registryHost
+                    + "] is INVALID.  Other machines must be able to use the address to reach this server." );
+            }
+
+            try
+            {
+                RemoteCacheServerFactory.startup( registryHost, registryPort, props );
+            }
+            catch ( IOException e )
+            {
+                log.error( "Problem starting remote cache server.", e );
+            }
+        }
+        catch ( UnknownHostException e )
+        {
+            log.error( "Could not get local address to use for the registry!", e );
+        }
+
+        return RemoteCacheServerFactory.getRemoteCacheServer();
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/server/RemoteCacheServerUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/server/RemoteCacheServerUnitTest.java
new file mode 100644
index 0000000..ca8ed9f
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/server/RemoteCacheServerUnitTest.java
@@ -0,0 +1,467 @@
+package org.apache.commons.jcs3.auxiliary.remote.server;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Properties;
+
+import org.apache.commons.jcs3.auxiliary.MockCacheEventLogger;
+import org.apache.commons.jcs3.auxiliary.remote.MockRemoteCacheListener;
+import org.apache.commons.jcs3.utils.timing.SleepUtil;
+import org.apache.commons.jcs3.auxiliary.remote.RemoteUtils;
+import org.apache.commons.jcs3.auxiliary.remote.server.RemoteCacheServer;
+import org.apache.commons.jcs3.auxiliary.remote.server.RemoteCacheServerAttributes;
+import org.apache.commons.jcs3.auxiliary.remote.server.behavior.IRemoteCacheServerAttributes;
+import org.apache.commons.jcs3.auxiliary.remote.server.behavior.RemoteType;
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+
+import junit.framework.TestCase;
+
+/**
+ * Since the server does not know that it is a server, it is easy to unit test. The factory does all
+ * the rmi work.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class RemoteCacheServerUnitTest
+    extends TestCase
+{
+    private static final String expectedIp1 = "adfasdf";
+    private static final String expectedIp2 = "adsfadsafaf";
+
+    private RemoteCacheServer<String, String> server;
+
+    @Override
+    protected void setUp() throws Exception
+    {
+        super.setUp();
+
+        IRemoteCacheServerAttributes rcsa = new RemoteCacheServerAttributes();
+        rcsa.setConfigFileName( "/TestRemoteCacheServer.ccf" );
+        Properties config = RemoteUtils.loadProps(rcsa.getConfigFileName());
+        this.server = new RemoteCacheServer<>( rcsa, config );
+    }
+
+    @Override
+    protected void tearDown() throws Exception
+    {
+        this.server.shutdown();
+
+        super.tearDown();
+    }
+
+    /**
+     * Add a listener. Pass the id of 0, verify that the server sets a new listener id. Do another
+     * and verify that the second gets an id of 2.
+     * <p>
+     * @throws Exception
+     */
+    public void testAddListenerToCache_LOCALtype()
+        throws Exception
+    {
+        MockRemoteCacheListener<String, String> mockListener1 = new MockRemoteCacheListener<>();
+        mockListener1.remoteType = RemoteType.LOCAL;
+        mockListener1.localAddress = expectedIp1;
+        MockRemoteCacheListener<String, String> mockListener2 = new MockRemoteCacheListener<>();
+        mockListener1.remoteType = RemoteType.LOCAL;
+        mockListener2.localAddress = expectedIp2;
+
+        String cacheName = "testAddListener";
+
+        // DO WORK
+        server.addCacheListener( cacheName, mockListener1 );
+        server.addCacheListener( cacheName, mockListener2 );
+
+        // VERIFY
+        assertEquals( "Wrong listener id.", 1, mockListener1.getListenerId() );
+        assertEquals( "Wrong listener id.", 2, mockListener2.getListenerId() );
+        assertEquals( "Wrong ip.", expectedIp1, server.getExtraInfoForRequesterId( 1 ) );
+        assertEquals( "Wrong ip.", expectedIp2, server.getExtraInfoForRequesterId( 2 ) );
+    }
+
+    /**
+     * Add a listener. Pass the id of 0, verify that the server sets a new listener id. Do another
+     * and verify that the second gets an id of 2.
+     * <p>
+     * @throws Exception
+     */
+    public void testAddListenerToCache_CLUSTERtype()
+        throws Exception
+    {
+        MockRemoteCacheListener<String, String> mockListener1 = new MockRemoteCacheListener<>();
+        mockListener1.remoteType = RemoteType.CLUSTER;
+        mockListener1.localAddress = expectedIp1;
+        MockRemoteCacheListener<String, String> mockListener2 = new MockRemoteCacheListener<>();
+        mockListener1.remoteType = RemoteType.CLUSTER;
+        mockListener2.localAddress = expectedIp2;
+
+        String cacheName = "testAddListener";
+
+        // DO WORK
+        server.addCacheListener( cacheName, mockListener1 );
+        server.addCacheListener( cacheName, mockListener2 );
+
+        // VERIFY
+        assertEquals( "Wrong listener id.", 1, mockListener1.getListenerId() );
+        assertEquals( "Wrong listener id.", 2, mockListener2.getListenerId() );
+        assertEquals( "Wrong ip.", expectedIp1, server.getExtraInfoForRequesterId( 1 ) );
+        assertEquals( "Wrong ip.", expectedIp2, server.getExtraInfoForRequesterId( 2 ) );
+    }
+
+    // TODO: This test only works if preconfigured remote caches exist. Need to fix.
+//    /**
+//     * Add a listener. Pass the id of 0, verify that the server sets a new listener id. Do another
+//     * and verify that the second gets an id of 2.
+//     * <p>
+//     * @throws Exception
+//     */
+//    public void testAddListener_ToAll()
+//        throws Exception
+//    {
+//        MockRemoteCacheListener<String, String> mockListener1 = new MockRemoteCacheListener<>();
+//        mockListener1.localAddress = expectedIp1;
+//        MockRemoteCacheListener<String, String> mockListener2 = new MockRemoteCacheListener<>();
+//        mockListener2.localAddress = expectedIp2;
+//
+//        // DO WORK
+//        // don't specify the cache name
+//        server.addCacheListener( mockListener1 );
+//        server.addCacheListener( mockListener2 );
+//
+//        // VERIFY
+//        assertEquals( "Wrong listener id.", 1, mockListener1.getListenerId() );
+//        assertEquals( "Wrong listener id.", 2, mockListener2.getListenerId() );
+//        assertEquals( "Wrong ip.", expectedIp1, server.getExtraInfoForRequesterId( 1 ) );
+//        assertEquals( "Wrong ip.", expectedIp2, server.getExtraInfoForRequesterId( 2 ) );
+//    }
+
+    /**
+     * Add a listener. Pass the id of 0, verify that the server sets a new listener id. Do another
+     * and verify that the second gets an id of 2. Call remove Listener and verify that it is
+     * removed.
+     * <p>
+     * @throws Exception
+     */
+    public void testAddListener_ToAllThenRemove()
+        throws Exception
+    {
+        MockRemoteCacheListener<String, String> mockListener1 = new MockRemoteCacheListener<>();
+        MockRemoteCacheListener<String, String> mockListener2 = new MockRemoteCacheListener<>();
+
+        String cacheName = "testAddListenerToAllThenRemove";
+
+        // DO WORK
+        server.addCacheListener( cacheName, mockListener1 );
+        server.addCacheListener( cacheName, mockListener2 );
+
+        // VERIFY
+        assertEquals( "Wrong number of listeners.", 2, server.getCacheListeners( cacheName ).eventQMap.size() );
+        assertEquals( "Wrong listener id.", 1, mockListener1.getListenerId() );
+        assertEquals( "Wrong listener id.", 2, mockListener2.getListenerId() );
+
+        // DO WORK
+        server.removeCacheListener( cacheName, mockListener1.getListenerId() );
+        assertEquals( "Wrong number of listeners.", 1, server.getCacheListeners( cacheName ).eventQMap.size() );
+    }
+
+    /**
+     * Add a listener. Pass the id of 0, verify that the server sets a new listener id. Do another
+     * and verify that the second gets an id of 2. Call remove Listener and verify that it is
+     * removed.
+     * <p>
+     * @throws Exception
+     */
+    public void testAddListener_ToAllThenRemove_clusterType()
+        throws Exception
+    {
+        MockRemoteCacheListener<String, String> mockListener1 = new MockRemoteCacheListener<>();
+        mockListener1.remoteType = RemoteType.CLUSTER;
+        MockRemoteCacheListener<String, String> mockListener2 = new MockRemoteCacheListener<>();
+        mockListener2.remoteType = RemoteType.CLUSTER;
+
+        String cacheName = "testAddListenerToAllThenRemove";
+
+        // DO WORK
+        server.addCacheListener( cacheName, mockListener1 );
+        server.addCacheListener( cacheName, mockListener2 );
+
+        // VERIFY
+        assertEquals( "Wrong number of listeners.", 0, server.getCacheListeners( cacheName ).eventQMap.size() );
+        assertEquals( "Wrong number of listeners.", 2, server.getClusterListeners( cacheName ).eventQMap.size() );
+        assertEquals( "Wrong listener id.", 1, mockListener1.getListenerId() );
+        assertEquals( "Wrong listener id.", 2, mockListener2.getListenerId() );
+
+        // DO WORK
+        server.removeCacheListener( cacheName, mockListener1.getListenerId() );
+        assertEquals( "Wrong number of listeners.", 1, server.getClusterListeners( cacheName ).eventQMap.size() );
+        assertNull( "Should be no entry in the ip map.", server.getExtraInfoForRequesterId( 1 ) );
+    }
+
+    /**
+     * Register a listener and then verify that it is called when we put using a different listener
+     * id.
+     * @throws Exception
+     */
+    public void testSimpleRegisterListenerAndPut()
+        throws Exception
+    {
+        IRemoteCacheServerAttributes rcsa = new RemoteCacheServerAttributes();
+        rcsa.setConfigFileName( "/TestRemoteCacheServer.ccf" );
+
+        Properties config = RemoteUtils.loadProps(rcsa.getConfigFileName());
+        MockRemoteCacheListener<String, Long> mockListener = new MockRemoteCacheListener<>();
+        RemoteCacheServer<String, Long> server = new RemoteCacheServer<>( rcsa, config );
+
+        String cacheName = "testSimpleRegisterListenerAndPut";
+        server.addCacheListener( cacheName, mockListener );
+
+        // DO WORK
+        List<ICacheElement<String, Long>> inputItems = new LinkedList<>();
+        int numToPut = 10;
+
+        for ( int i = 0; i < numToPut; i++ )
+        {
+            ICacheElement<String, Long> element = new CacheElement<>( cacheName, String.valueOf( i ), Long.valueOf( i ) );
+            inputItems.add( element );
+            server.update( element, 9999 );
+        }
+
+        Thread.sleep( 100 );
+        Thread.yield();
+        Thread.sleep( 100 );
+
+        // VERIFY
+        assertEquals( "Wrong number of items put to listener.", numToPut, mockListener.putItems.size() );
+        for ( int i = 0; i < numToPut; i++ )
+        {
+            assertEquals( "Wrong item.", inputItems.get( i ), mockListener.putItems.get( i ) );
+        }
+
+        server.shutdown();
+    }
+
+    /**
+     * Register a listener and then verify that it is called when we put using a different listener
+     * id. The updates should come from a cluster listener and local cluster consistency should be
+     * true.
+     * <p>
+     * @throws Exception
+     */
+    public void testSimpleRegisterListenerAndPut_FromClusterWithLCC()
+        throws Exception
+    {
+        // SETUP
+        IRemoteCacheServerAttributes rcsa = new RemoteCacheServerAttributes();
+        rcsa.setLocalClusterConsistency( true );
+        rcsa.setConfigFileName( "/TestRemoteCacheServer.ccf" );
+        Properties config = RemoteUtils.loadProps(rcsa.getConfigFileName());
+        RemoteCacheServer<String, Long> server = new RemoteCacheServer<>( rcsa, config );
+
+        // this is to get the listener id for inserts.
+        MockRemoteCacheListener<String, Long> clusterListener = new MockRemoteCacheListener<>();
+        clusterListener.remoteType = RemoteType.CLUSTER;
+
+        // this should get the updates
+        MockRemoteCacheListener<String, Long> localListener = new MockRemoteCacheListener<>();
+        localListener.remoteType = RemoteType.LOCAL;
+
+        String cacheName = "testSimpleRegisterListenerAndPut_FromClusterWithLCC";
+        server.addCacheListener( cacheName, clusterListener );
+        server.addCacheListener( cacheName, localListener );
+
+        // DO WORK
+        List<ICacheElement<String, Long>> inputItems = new LinkedList<>();
+        int numToPut = 10;
+
+        for ( int i = 0; i < numToPut; i++ )
+        {
+            ICacheElement<String, Long> element = new CacheElement<>( cacheName, String.valueOf( i ), Long.valueOf( i ) );
+            inputItems.add( element );
+            // update using the cluster listener id
+            server.update( element, clusterListener.getListenerId() );
+        }
+
+        SleepUtil.sleepAtLeast( 200 );
+        Thread.yield();
+        SleepUtil.sleepAtLeast( 200 );
+
+        // VERIFY
+        assertEquals( "Wrong number of items put to listener.", numToPut, localListener.putItems.size() );
+        for ( int i = 0; i < numToPut; i++ )
+        {
+            assertEquals( "Wrong item.", inputItems.get( i ), localListener.putItems.get( i ) );
+        }
+
+        server.shutdown();
+    }
+
+    /**
+     * Register a listener and then verify that it is called when we put using a different listener
+     * id.
+     * @throws Exception
+     */
+    public void testSimpleRegisterListenerAndRemove()
+        throws Exception
+    {
+        MockRemoteCacheListener<String, String> mockListener = new MockRemoteCacheListener<>();
+
+        String cacheName = "testSimpleRegisterListenerAndPut";
+        server.addCacheListener( cacheName, mockListener );
+
+        // DO WORK
+        int numToPut = 10;
+
+        for ( int i = 0; i < numToPut; i++ )
+        {
+            // use a junk listener id
+            server.remove( cacheName, String.valueOf( i ), 9999 );
+        }
+
+        Thread.sleep( 100 );
+        Thread.yield();
+        Thread.sleep( 100 );
+
+        // VERIFY
+        assertEquals( "Wrong number of items removed from listener.", numToPut, mockListener.removedKeys.size() );
+        for ( int i = 0; i < numToPut; i++ )
+        {
+            assertEquals( "Wrong key.", String.valueOf( i ), mockListener.removedKeys.get( i ) );
+        }
+    }
+
+    /**
+     * Verify event log calls.
+     * <p>
+     * @throws Exception
+     */
+    public void testUpdate_simple()
+        throws Exception
+    {
+        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
+        server.setCacheEventLogger( cacheEventLogger );
+
+        ICacheElement<String, String> item = new CacheElement<>( "region", "key", "value" );
+
+        // DO WORK
+        server.update( item );
+
+        // VERIFY
+        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
+        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
+    }
+
+    /**
+     * Verify event log calls.
+     * <p>
+     * @throws Exception
+     */
+    public void testGet_simple()
+        throws Exception
+    {
+        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
+        server.setCacheEventLogger( cacheEventLogger );
+
+        // DO WORK
+        server.get( "region", "key" );
+
+        // VERIFY
+        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
+        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
+    }
+
+    /**
+     * Verify event log calls.
+     * <p>
+     * @throws Exception
+     */
+    public void testGetMatching_simple()
+        throws Exception
+    {
+        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
+        server.setCacheEventLogger( cacheEventLogger );
+
+        // DO WORK
+        server.getMatching( "region", "pattern", 0 );
+
+        // VERIFY
+        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
+        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
+    }
+
+    /**
+     * Verify event log calls.
+     * <p>
+     * @throws Exception
+     */
+    public void testGetMultiple_simple()
+        throws Exception
+    {
+        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
+        server.setCacheEventLogger( cacheEventLogger );
+
+        // DO WORK
+        server.getMultiple( "region", new HashSet<>() );
+
+        // VERIFY
+        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
+        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
+    }
+
+    /**
+     * Verify event log calls.
+     * <p>
+     * @throws Exception
+     */
+    public void testRemove_simple()
+        throws Exception
+    {
+        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
+        server.setCacheEventLogger( cacheEventLogger );
+
+        // DO WORK
+        server.remove( "region", "key" );
+
+        // VERIFY
+        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
+        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
+    }
+
+    /**
+     * Verify event log calls.
+     * <p>
+     * @throws Exception
+     */
+    public void testRemoveAll_simple()
+        throws Exception
+    {
+        MockCacheEventLogger cacheEventLogger = new MockCacheEventLogger();
+        server.setCacheEventLogger( cacheEventLogger );
+
+        // DO WORK
+        server.removeAll( "region" );
+
+        // VERIFY
+        assertEquals( "Start should have been called.", 1, cacheEventLogger.startICacheEventCalls );
+        assertEquals( "End should have been called.", 1, cacheEventLogger.endICacheEventCalls );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/server/TimeoutConfigurableRMISocketFactoryUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/server/TimeoutConfigurableRMISocketFactoryUnitTest.java
new file mode 100644
index 0000000..4e91feb
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/server/TimeoutConfigurableRMISocketFactoryUnitTest.java
@@ -0,0 +1,55 @@
+package org.apache.commons.jcs3.auxiliary.remote.server;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+import org.apache.commons.jcs3.auxiliary.remote.server.TimeoutConfigurableRMISocketFactory;
+
+/** Unit tests for the custom factory */
+public class TimeoutConfigurableRMISocketFactoryUnitTest
+    extends TestCase
+{
+    /**
+     * Simple test to see that we can create a server socket and connect.
+     * <p>
+     * @throws IOException
+     */
+    public void testCreateAndConnect() throws IOException
+    {
+        // SETUP
+        int port = 3455;
+        String host = "localhost";
+        TimeoutConfigurableRMISocketFactory factory = new TimeoutConfigurableRMISocketFactory();
+
+        // DO WORK
+        ServerSocket serverSocket = factory.createServerSocket( port );
+        Socket socket = factory.createSocket( host, port );
+        socket.close();
+        serverSocket.close();
+
+        // VERIFY
+        // passive, no errors
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/util/RemoteCacheRequestFactoryUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/util/RemoteCacheRequestFactoryUnitTest.java
new file mode 100644
index 0000000..3fac53d
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/remote/util/RemoteCacheRequestFactoryUnitTest.java
@@ -0,0 +1,146 @@
+package org.apache.commons.jcs3.auxiliary.remote.util;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Set;
+
+import org.apache.commons.jcs3.auxiliary.remote.util.RemoteCacheRequestFactory;
+import org.apache.commons.jcs3.auxiliary.remote.value.RemoteCacheRequest;
+import org.apache.commons.jcs3.auxiliary.remote.value.RemoteRequestType;
+import org.apache.commons.jcs3.engine.CacheElement;
+
+/** Unit tests for the request creator. */
+public class RemoteCacheRequestFactoryUnitTest
+    extends TestCase
+{
+    /** Simple test */
+    public void testCreateGetRequest_Normal()
+    {
+        // SETUP
+        String cacheName = "test";
+        Serializable key = "key";
+        long requesterId = 2;
+
+        // DO WORK
+        RemoteCacheRequest<Serializable, Serializable> result =
+            RemoteCacheRequestFactory.createGetRequest( cacheName, key, requesterId );
+
+        // VERIFY
+        assertNotNull( "Should have a result", result );
+        assertEquals( "Wrong cacheName", cacheName, result.getCacheName() );
+        assertEquals( "Wrong type", RemoteRequestType.GET, result.getRequestType() );
+    }
+
+    /** Simple test */
+    public void testCreateGetMatchingRequest_Normal()
+    {
+        // SETUP
+        String cacheName = "test";
+        String pattern = "pattern";
+        long requesterId = 2;
+
+        // DO WORK
+        RemoteCacheRequest<Serializable, Serializable> result =
+            RemoteCacheRequestFactory.createGetMatchingRequest( cacheName, pattern, requesterId );
+
+        // VERIFY
+        assertNotNull( "Should have a result", result );
+        assertEquals( "Wrong cacheName", cacheName, result.getCacheName() );
+        assertEquals( "Wrong type", RemoteRequestType.GET_MATCHING, result.getRequestType() );
+    }
+
+    /** Simple test */
+    public void testCreateGetMultipleRequest_Normal()
+    {
+        // SETUP
+        String cacheName = "test";
+        Set<Serializable> keys = Collections.emptySet();
+        long requesterId = 2;
+
+        // DO WORK
+        RemoteCacheRequest<Serializable, Serializable> result =
+            RemoteCacheRequestFactory.createGetMultipleRequest( cacheName, keys, requesterId );
+
+        // VERIFY
+        assertNotNull( "Should have a result", result );
+        assertEquals( "Wrong cacheName", cacheName, result.getCacheName() );
+        assertEquals( "Wrong type", RemoteRequestType.GET_MULTIPLE, result.getRequestType() );
+    }
+
+    /** Simple test */
+    public void testCreateRemoveRequest_Normal()
+    {
+        // SETUP
+        String cacheName = "test";
+        Serializable key = "key";
+        long requesterId = 2;
+
+        // DO WORK
+        RemoteCacheRequest<Serializable, Serializable> result = RemoteCacheRequestFactory
+            .createRemoveRequest( cacheName, key, requesterId );
+
+        // VERIFY
+        assertNotNull( "Should have a result", result );
+        assertEquals( "Wrong cacheName", cacheName, result.getCacheName() );
+        assertEquals( "Wrong type", RemoteRequestType.REMOVE, result.getRequestType() );
+    }
+
+    /** Simple test */
+    public void testCreateRemoveAllRequest_Normal()
+    {
+        // SETUP
+        String cacheName = "test";
+        long requesterId = 2;
+
+        // DO WORK
+        RemoteCacheRequest<Serializable, Serializable> result =
+            RemoteCacheRequestFactory.createRemoveAllRequest( cacheName, requesterId );
+
+        // VERIFY
+        assertNotNull( "Should have a result", result );
+        assertEquals( "Wrong cacheName", cacheName, result.getCacheName() );
+        assertEquals( "Wrong type", RemoteRequestType.REMOVE_ALL, result.getRequestType() );
+    }
+
+    /** Simple test */
+    public void testCreateUpdateRequest_Normal()
+    {
+        // SETUP
+        String cacheName = "test";
+        Serializable key = "key";
+        long requesterId = 2;
+
+        CacheElement<Serializable, Serializable> element =
+            new CacheElement<>( cacheName, key, null );
+
+        // DO WORK
+        RemoteCacheRequest<Serializable, Serializable> result =
+            RemoteCacheRequestFactory.createUpdateRequest( element, requesterId );
+
+        // VERIFY
+        assertNotNull( "Should have a result", result );
+        assertEquals( "Wrong cacheName", cacheName, result.getCacheName() );
+        assertEquals( "Wrong type", RemoteRequestType.UPDATE, result.getRequestType() );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/CacheEventQueueFactoryUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/CacheEventQueueFactoryUnitTest.java
new file mode 100644
index 0000000..23dec3e
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/CacheEventQueueFactoryUnitTest.java
@@ -0,0 +1,69 @@
+package org.apache.commons.jcs3.engine;
+
+import org.apache.commons.jcs3.auxiliary.remote.MockRemoteCacheListener;
+import org.apache.commons.jcs3.engine.CacheEventQueueFactory;
+import org.apache.commons.jcs3.engine.behavior.ICacheEventQueue;
+import org.apache.commons.jcs3.engine.behavior.ICacheListener;
+import org.apache.commons.jcs3.engine.behavior.ICacheEventQueue.QueueType;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/** Unit tests for the CacheEventQueueFactory */
+public class CacheEventQueueFactoryUnitTest
+    extends TestCase
+{
+    /** Test create */
+    public void testCreateCacheEventQueue_Single()
+    {
+        // SETUP
+        QueueType eventQueueType = QueueType.SINGLE;
+        ICacheListener<String, String> listener = new MockRemoteCacheListener<>();
+        long listenerId = 1;
+
+        CacheEventQueueFactory<String, String> factory = new CacheEventQueueFactory<>();
+
+        // DO WORK
+        ICacheEventQueue<String, String> result = factory.createCacheEventQueue( listener, listenerId, "cacheName", "threadPoolName", eventQueueType );
+
+        // VERIFY
+        assertNotNull( "Should have a result", result );
+        assertTrue( "Wrong type", result.getQueueType() == QueueType.SINGLE );
+    }
+
+    /** Test create */
+    public void testCreateCacheEventQueue_Pooled()
+    {
+        // SETUP
+        QueueType eventQueueType = QueueType.POOLED;
+        ICacheListener<String, String> listener = new MockRemoteCacheListener<>();
+        long listenerId = 1;
+
+        CacheEventQueueFactory<String, String> factory = new CacheEventQueueFactory<>();
+
+        // DO WORK
+        ICacheEventQueue<String, String> result = factory.createCacheEventQueue( listener, listenerId, "cacheName", "threadPoolName", eventQueueType );
+
+        // VERIFY
+        assertNotNull( "Should have a result", result );
+        assertTrue( "Wrong type", result.getQueueType() == QueueType.POOLED );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/ElementAttributesUtils.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/ElementAttributesUtils.java
new file mode 100644
index 0000000..82743ba
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/ElementAttributesUtils.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.engine;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.engine.ElementAttributes;
+
+/**
+ * Allow test access to set last access time without exposing public method
+ */
+public class ElementAttributesUtils {
+    public static void setLastAccessTime(ElementAttributes ea, long time) {
+        ea.setLastAccessTime(time);
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/EventQueueConcurrentLoadTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/EventQueueConcurrentLoadTest.java
new file mode 100644
index 0000000..f219f89
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/EventQueueConcurrentLoadTest.java
@@ -0,0 +1,360 @@
+package org.apache.commons.jcs3.engine;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.CacheEventQueue;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheListener;
+
+import junit.extensions.ActiveTestSuite;
+import junit.framework.Test;
+import junit.framework.TestCase;
+
+/**
+ * This test case is designed to makes sure there are no deadlocks in the event queue. The time to
+ * live should be set to a very short interval to make a deadlock more likely.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class EventQueueConcurrentLoadTest
+    extends TestCase
+{
+    /** The queue implementation */
+    private static CacheEventQueue<String, String> queue = null;
+
+    /** The mock listener */
+    private static CacheListenerImpl<String, String> listen = null;
+
+    /** max failure setting */
+    private final int maxFailure = 3;
+
+    /** time to wait before retrying on failure. */
+    private final int waitBeforeRetry = 100;
+
+    /** very small idle time */
+    private final int idleTime = 2;
+
+    /**
+     * Constructor for the TestDiskCache object.
+     * @param testName
+     */
+    public EventQueueConcurrentLoadTest( String testName )
+    {
+        super( testName );
+    }
+
+    /**
+     * Main method passes this test to the text test runner.
+     * @param args
+     */
+    public static void main( String args[] )
+    {
+        String[] testCaseName = { EventQueueConcurrentLoadTest.class.getName() };
+        junit.textui.TestRunner.main( testCaseName );
+    }
+
+    /**
+     * A unit test suite for JUnit
+     * @return The test suite
+     */
+    public static Test suite()
+    {
+        ActiveTestSuite suite = new ActiveTestSuite();
+
+        suite.addTest( new EventQueueConcurrentLoadTest( "testRunPutTest1" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runPutTest( 200, 200 );
+            }
+        } );
+
+        suite.addTest( new EventQueueConcurrentLoadTest( "testRunPutTest2" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runPutTest( 1200, 1400 );
+            }
+        } );
+
+        suite.addTest( new EventQueueConcurrentLoadTest( "testRunRemoveTest1" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runRemoveTest( 2200 );
+            }
+        } );
+
+        suite.addTest( new EventQueueConcurrentLoadTest( "testRunPutTest4" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runPutTest( 5200, 6600 );
+            }
+        } );
+
+        suite.addTest( new EventQueueConcurrentLoadTest( "testRunRemoveTest2" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runRemoveTest( 5200 );
+            }
+        } );
+
+        suite.addTest( new EventQueueConcurrentLoadTest( "testRunPutDelayTest" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runPutDelayTest( 100, 6700 );
+            }
+        } );
+
+        return suite;
+    }
+
+    /**
+     * Test setup. Create the static queue to be used by all tests
+     */
+    @Override
+    public void setUp()
+    {
+        listen = new CacheListenerImpl<>();
+        queue = new CacheEventQueue<>( listen, 1L, "testCache1", maxFailure, waitBeforeRetry );
+
+        queue.setWaitToDieMillis( idleTime );
+    }
+
+    /**
+     * Adds put events to the queue.
+     * @param end
+     * @param expectedPutCount
+     * @throws Exception
+     */
+    public void runPutTest( int end, int expectedPutCount )
+        throws Exception
+    {
+        for ( int i = 0; i <= end; i++ )
+        {
+            CacheElement<String, String> elem = new CacheElement<>( "testCache1", i + ":key", i + "data" );
+            queue.addPutEvent( elem );
+        }
+
+        while ( !queue.isEmpty() )
+        {
+            synchronized ( this )
+            {
+                System.out.println( "queue is still busy, waiting 250 millis" );
+                this.wait( 250 );
+            }
+        }
+        System.out.println( "queue is empty, comparing putCount" );
+
+        // this becomes less accurate with each test. It should never fail. If
+        // it does things are very off.
+        assertTrue( "The put count [" + listen.putCount + "] is below the expected minimum threshold ["
+            + expectedPutCount + "]", listen.putCount >= ( expectedPutCount - 1 ) );
+
+    }
+
+    /**
+     * Add remove events to the event queue.
+     * @param end
+     * @throws Exception
+     */
+    public void runRemoveTest( int end )
+        throws Exception
+    {
+        for ( int i = 0; i <= end; i++ )
+        {
+            queue.addRemoveEvent( i + ":key" );
+        }
+
+    }
+
+    /**
+     * Test putting and a delay. Waits until queue is empty to start.
+     * @param end
+     * @param expectedPutCount
+     * @throws Exception
+     */
+    public void runPutDelayTest( int end, int expectedPutCount )
+        throws Exception
+    {
+        while ( !queue.isEmpty() )
+        {
+            synchronized ( this )
+            {
+                System.out.println( "queue is busy, waiting 250 millis to begin" );
+                this.wait( 250 );
+            }
+        }
+        System.out.println( "queue is empty, begin" );
+
+        // get it going
+        CacheElement<String, String> elem = new CacheElement<>( "testCache1", "a:key", "adata" );
+        queue.addPutEvent( elem );
+
+        for ( int i = 0; i <= end; i++ )
+        {
+            synchronized ( this )
+            {
+                if ( i % 2 == 0 )
+                {
+                    this.wait( idleTime );
+                }
+                else
+                {
+                    this.wait( idleTime / 2 );
+                }
+            }
+            CacheElement<String, String> elem2 = new CacheElement<>( "testCache1", i + ":key", i + "data" );
+            queue.addPutEvent( elem2 );
+        }
+
+        while ( !queue.isEmpty() )
+        {
+            synchronized ( this )
+            {
+                System.out.println( "queue is still busy, waiting 250 millis" );
+                this.wait( 250 );
+            }
+        }
+        System.out.println( "queue is empty, comparing putCount" );
+
+        Thread.sleep( 1000 );
+
+        // this becomes less accurate with each test. It should never fail. If
+        // it does things are very off.
+        assertTrue( "The put count [" + listen.putCount + "] is below the expected minimum threshold ["
+            + expectedPutCount + "]", listen.putCount >= ( expectedPutCount - 1 ) );
+
+    }
+
+    /**
+     * This is a dummy cache listener to use when testing the event queue.
+     */
+    protected static class CacheListenerImpl<K, V>
+        implements ICacheListener<K, V>
+    {
+        /**
+         * <code>putCount</code>
+         */
+        protected int putCount = 0;
+
+        /**
+         * <code>removeCount</code>
+         */
+        protected int removeCount = 0;
+
+        /**
+         * @param item
+         * @throws IOException
+         */
+        @Override
+        public void handlePut( ICacheElement<K, V> item )
+            throws IOException
+        {
+            synchronized ( this )
+            {
+                putCount++;
+            }
+        }
+
+        /**
+         * @param cacheName
+         * @param key
+         * @throws IOException
+         */
+        @Override
+        public void handleRemove( String cacheName, K key )
+            throws IOException
+        {
+            synchronized ( this )
+            {
+                removeCount++;
+            }
+
+        }
+
+        /**
+         * @param cacheName
+         * @throws IOException
+         */
+        @Override
+        public void handleRemoveAll( String cacheName )
+            throws IOException
+        {
+            // TODO Auto-generated method stub
+
+        }
+
+        /**
+         * @param cacheName
+         * @throws IOException
+         */
+        @Override
+        public void handleDispose( String cacheName )
+            throws IOException
+        {
+            // TODO Auto-generated method stub
+
+        }
+
+        /**
+         * @param id
+         * @throws IOException
+         */
+        @Override
+        public void setListenerId( long id )
+            throws IOException
+        {
+            // TODO Auto-generated method stub
+
+        }
+
+        /**
+         * @return 0
+         * @throws IOException
+         */
+        @Override
+        public long getListenerId()
+            throws IOException
+        {
+            // TODO Auto-generated method stub
+            return 0;
+        }
+
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/MockCacheServiceNonLocal.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/MockCacheServiceNonLocal.java
new file mode 100644
index 0000000..6496869
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/MockCacheServiceNonLocal.java
@@ -0,0 +1,245 @@
+package org.apache.commons.jcs3.engine;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheServiceNonLocal;
+
+/**
+ * This is a mock impl of the non local cache service.
+ */
+public class MockCacheServiceNonLocal<K, V>
+    implements ICacheServiceNonLocal<K, V>
+{
+    /** The key last passed to get */
+    public K lastGetKey;
+
+    /** The pattern last passed to get */
+    public String lastGetMatchingPattern;
+
+    /** The keya last passed to getMatching */
+    public Set<K> lastGetMultipleKeys;
+
+    /** The object that was last passed to update. */
+    public ICacheElement<K, V> lastUpdate;
+
+    /** List of updates. */
+    public List<ICacheElement<K, V>> updateRequestList = new ArrayList<>();
+
+    /** List of request ids. */
+    public List<Long> updateRequestIdList = new ArrayList<>();
+
+    /** The key that was last passed to remove. */
+    public K lastRemoveKey;
+
+    /** The cache name that was last passed to removeAll. */
+    public String lastRemoveAllCacheName;
+
+    /**
+     * @param cacheName
+     * @param key
+     * @param requesterId - identity of requester
+     * @return null
+     */
+    @Override
+    public ICacheElement<K, V> get( String cacheName, K key, long requesterId )
+    {
+        lastGetKey = key;
+        return null;
+    }
+
+    /**
+     * @param cacheName
+     * @return empty set
+     */
+    @Override
+    public Set<K> getKeySet( String cacheName )
+    {
+        return new HashSet<>();
+    }
+
+    /**
+     * Set the last remove key.
+     * <p>
+     * @param cacheName
+     * @param key
+     * @param requesterId - identity of requester
+     */
+    @Override
+    public void remove( String cacheName, K key, long requesterId )
+    {
+        lastRemoveKey = key;
+    }
+
+    /**
+     * Set the lastRemoveAllCacheName to the cacheName.
+     * <p>
+     * @param cacheName - region name
+     * @param requesterId - identity of requester
+     * @throws IOException
+     */
+    @Override
+    public void removeAll( String cacheName, long requesterId )
+        throws IOException
+    {
+        lastRemoveAllCacheName = cacheName;
+    }
+
+    /**
+     * Set the last update item.
+     * <p>
+     * @param item
+     * @param requesterId - identity of requester
+     */
+    @Override
+    public void update( ICacheElement<K, V> item, long requesterId )
+    {
+        lastUpdate = item;
+        updateRequestList.add( item );
+        updateRequestIdList.add( Long.valueOf( requesterId ) );
+    }
+
+    /**
+     * Do nothing.
+     * <p>
+     * @param cacheName
+     */
+    @Override
+    public void dispose( String cacheName )
+    {
+        return;
+    }
+
+    /**
+     * @param cacheName
+     * @param key
+     * @return null
+     */
+    @Override
+    public ICacheElement<K, V> get( String cacheName, K key )
+    {
+        return get( cacheName, key, 0 );
+    }
+
+    /**
+     * Do nothing.
+     */
+    @Override
+    public void release()
+    {
+        return;
+    }
+
+    /**
+     * Set the last remove key.
+     * <p>
+     * @param cacheName
+     * @param key
+     */
+    @Override
+    public void remove( String cacheName, K key )
+    {
+        lastRemoveKey = key;
+    }
+
+    /**
+     * Set the last remove all cache name.
+     * <p>
+     * @param cacheName
+     */
+    @Override
+    public void removeAll( String cacheName )
+    {
+        lastRemoveAllCacheName = cacheName;
+    }
+
+    /**
+     * Set the last update item.
+     * <p>
+     * @param item
+     */
+    @Override
+    public void update( ICacheElement<K, V> item )
+    {
+        lastUpdate = item;
+    }
+
+    /**
+     * @param cacheName
+     * @param keys
+     * @param requesterId - identity of requester
+     * @return empty map
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys, long requesterId )
+    {
+        lastGetMultipleKeys = keys;
+        return new HashMap<>();
+    }
+
+    /**
+     * @param cacheName
+     * @param keys
+     * @return empty map
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMultiple( String cacheName, Set<K> keys )
+    {
+        return getMultiple( cacheName, keys, 0 );
+    }
+
+    /**
+     * Returns an empty map. Zombies have no internal data.
+     * <p>
+     * @param cacheName
+     * @param pattern
+     * @return an empty map
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern )
+        throws IOException
+    {
+        return getMatching( cacheName, pattern, 0 );
+    }
+
+    /**
+     * @param cacheName
+     * @param pattern
+     * @param requesterId
+     * @return Map
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMatching( String cacheName, String pattern, long requesterId )
+        throws IOException
+    {
+        lastGetMatchingPattern = pattern;
+        return new HashMap<>();
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/SystemPropertyUsageUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/SystemPropertyUsageUnitTest.java
new file mode 100644
index 0000000..146da95
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/SystemPropertyUsageUnitTest.java
@@ -0,0 +1,108 @@
+package org.apache.commons.jcs3.engine;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+import org.apache.commons.jcs3.utils.props.PropertyLoader;
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.engine.control.CompositeCacheManager;
+
+import java.util.Properties;
+
+/**
+ * Verify that system properties can override.
+ */
+public class SystemPropertyUsageUnitTest
+    extends TestCase
+{
+    private static final String JCS_DEFAULT_CACHEATTRIBUTES_MAX_OBJECTS = "jcs.default.cacheattributes.MaxObjects";
+    private static final int testValue = 6789;
+
+    private CompositeCacheManager manager = null;
+
+    @Override
+    protected void setUp() throws Exception
+    {
+       super.setUp();
+       //First shut down any previously running manager.
+       manager = CompositeCacheManager.getInstance();
+       manager.shutDown();
+    }
+
+	/**
+	 * @see junit.framework.TestCase#tearDown()
+	 */
+	@Override
+	protected void tearDown() throws Exception
+	{
+		if (manager != null)
+		{
+			manager.shutDown();
+		}
+
+        System.clearProperty(JCS_DEFAULT_CACHEATTRIBUTES_MAX_OBJECTS);
+		super.tearDown();
+	}
+
+	/**
+     * Verify that the system properties are used.
+     * @throws Exception
+     *
+     */
+    public void testSystemPropertyUsage()
+        throws Exception
+    {
+        System.setProperty( JCS_DEFAULT_CACHEATTRIBUTES_MAX_OBJECTS, String.valueOf(testValue) );
+
+        JCS.setConfigFilename( "/TestSystemPropertyUsage.ccf" );
+
+        CacheAccess<String, String> jcs = JCS.getInstance( "someCacheNotInFile" );
+
+        manager = CompositeCacheManager.getInstance();
+
+        assertEquals( "System property value is not reflected.", testValue, jcs.getCacheAttributes().getMaxObjects());
+    }
+
+    /**
+     * Verify that the system properties are not used is specified.
+     *
+     * @throws Exception
+     *
+     */
+    public void testSystemPropertyUsage_inactive()
+        throws Exception
+    {
+        System.setProperty( JCS_DEFAULT_CACHEATTRIBUTES_MAX_OBJECTS, String.valueOf(testValue) );
+
+        manager = CompositeCacheManager.getUnconfiguredInstance();
+
+        Properties props = PropertyLoader.loadProperties( "TestSystemPropertyUsage.ccf" );
+
+        manager.configure( props, false );
+
+        CacheAccess<String, String> jcs = JCS.getInstance( "someCacheNotInFile" );
+
+        assertEquals( "System property value should not be reflected",
+                      Integer.parseInt( props.getProperty( JCS_DEFAULT_CACHEATTRIBUTES_MAX_OBJECTS ) ),
+                      jcs.getCacheAttributes().getMaxObjects());
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/ZombieCacheServiceNonLocalUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/ZombieCacheServiceNonLocalUnitTest.java
new file mode 100644
index 0000000..1d2b89b
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/ZombieCacheServiceNonLocalUnitTest.java
@@ -0,0 +1,128 @@
+package org.apache.commons.jcs3.engine;
+
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.ZombieCacheServiceNonLocal;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for the zombie remote cache service.
+ */
+public class ZombieCacheServiceNonLocalUnitTest
+    extends TestCase
+{
+    /**
+     * Verify that an update event gets added and then is sent to the service passed to propagate.
+     * <p>
+     * @throws Exception
+     */
+    public void testUpdateThenWalk()
+        throws Exception
+    {
+        // SETUP
+        MockCacheServiceNonLocal<String, String> service = new MockCacheServiceNonLocal<>();
+
+        ZombieCacheServiceNonLocal<String, String> zombie = new ZombieCacheServiceNonLocal<>( 10 );
+
+        String cacheName = "testUpdate";
+
+        // DO WORK
+        ICacheElement<String, String> element = new CacheElement<>( cacheName, "key", "value" );
+        zombie.update( element, 123l );
+        zombie.propagateEvents( service );
+
+        // VERIFY
+        assertEquals( "Updated element is not as expected.", element, service.lastUpdate );
+    }
+
+    /**
+     * Verify that nothing is added if the max is set to 0.
+     * <p>
+     * @throws Exception
+     */
+    public void testUpdateThenWalk_zeroSize()
+        throws Exception
+    {
+        // SETUP
+        MockCacheServiceNonLocal<String, String> service = new MockCacheServiceNonLocal<>();
+
+        ZombieCacheServiceNonLocal<String, String> zombie = new ZombieCacheServiceNonLocal<>( 0 );
+
+        String cacheName = "testUpdate";
+
+        // DO WORK
+        ICacheElement<String, String> element = new CacheElement<>( cacheName, "key", "value" );
+        zombie.update( element, 123l );
+        zombie.propagateEvents( service );
+
+        // VERIFY
+        assertNull( "Nothing should have been put to the service.", service.lastUpdate );
+    }
+
+    /**
+     * Verify that a remove event gets added and then is sent to the service passed to propagate.
+     * <p>
+     * @throws Exception
+     */
+    public void testRemoveThenWalk()
+        throws Exception
+    {
+        // SETUP
+        MockCacheServiceNonLocal<String, String> service = new MockCacheServiceNonLocal<>();
+
+        ZombieCacheServiceNonLocal<String, String> zombie = new ZombieCacheServiceNonLocal<>( 10 );
+
+        String cacheName = "testRemoveThenWalk";
+        String key = "myKey";
+
+        // DO WORK
+        zombie.remove( cacheName, key, 123l );
+        zombie.propagateEvents( service );
+
+        // VERIFY
+        assertEquals( "Updated element is not as expected.", key, service.lastRemoveKey );
+    }
+
+    /**
+     * Verify that a removeAll event gets added and then is sent to the service passed to propagate.
+     * <p>
+     * @throws Exception
+     */
+    public void testRemoveAllThenWalk()
+        throws Exception
+    {
+        // SETUP
+        MockCacheServiceNonLocal<String, String> service = new MockCacheServiceNonLocal<>();
+
+        ZombieCacheServiceNonLocal<String, String> zombie = new ZombieCacheServiceNonLocal<>( 10 );
+
+        String cacheName = "testRemoveThenWalk";
+
+        // DO WORK
+        zombie.removeAll( cacheName, 123l );
+        zombie.propagateEvents( service );
+
+        // VERIFY
+        assertEquals( "Updated element is not as expected.", cacheName, service.lastRemoveAllCacheName );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/CacheManagerStatsUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/CacheManagerStatsUnitTest.java
new file mode 100644
index 0000000..e9fc3d8
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/CacheManagerStatsUnitTest.java
@@ -0,0 +1,73 @@
+package org.apache.commons.jcs3.engine.control;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.engine.control.CompositeCacheManager;
+import org.apache.commons.jcs3.engine.stats.behavior.ICacheStats;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/**
+ * @author Aaron Smuts
+ *
+ */
+public class CacheManagerStatsUnitTest
+    extends TestCase
+{
+
+    /**
+     * Just get the stats after putting a couple entries in the cache.
+     *
+     * @throws Exception
+     */
+    public void testSimpleGetStats() throws Exception
+    {
+        CacheAccess<String, String> cache = JCS.getInstance( "testCache1" );
+
+        // 1 miss, 1 hit, 1 put
+        cache.get( "testKey" );
+        cache.put( "testKey", "testdata" );
+        // should have 4 hits
+        cache.get( "testKey" );
+        cache.get( "testKey" );
+        cache.get( "testKey" );
+        cache.get( "testKey" );
+
+        CompositeCacheManager mgr = CompositeCacheManager.getInstance();
+        String statsString = mgr.getStats();
+
+//        System.out.println( statsString );
+
+        assertTrue( "Should have the cacheName in here.", statsString.indexOf("testCache1") != -1 );
+        assertTrue( "Should have the HitCountRam in here.", statsString.indexOf("HitCountRam") != -1 );
+        assertTrue( "Should have the 4 in here.", statsString.indexOf("4") != -1 );
+
+        ICacheStats[] stats = mgr.getStatistics();
+        int statsLen = stats.length;
+//        System.out.println( "statsLen = " + statsLen );
+        for ( int i = 0; i < statsLen; i++ )
+        {
+            // TODO finish
+        }
+    }
+
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/CompositeCacheConfiguratorUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/CompositeCacheConfiguratorUnitTest.java
new file mode 100644
index 0000000..b0481e2
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/CompositeCacheConfiguratorUnitTest.java
@@ -0,0 +1,95 @@
+package org.apache.commons.jcs3.engine.control;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Properties;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.jcs3.auxiliary.MockAuxiliaryCache;
+import org.apache.commons.jcs3.auxiliary.MockAuxiliaryCacheAttributes;
+import org.apache.commons.jcs3.auxiliary.MockAuxiliaryCacheFactory;
+import org.apache.commons.jcs3.engine.logging.MockCacheEventLogger;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCache;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheConfigurator;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+import org.apache.commons.jcs3.engine.control.CompositeCacheConfigurator;
+import org.apache.commons.jcs3.engine.control.CompositeCacheManager;
+
+/** Unit tests for the configurator. */
+public class CompositeCacheConfiguratorUnitTest
+    extends TestCase
+{
+    /**
+     * Verify that we can parse the event logger correctly
+     */
+    public void testParseAuxiliary_CacheEventLogger_Normal()
+    {
+        // SETUP
+        String regionName = "MyRegion";
+
+        String auxName = "MockAux";
+        String auxPrefix = CompositeCacheConfigurator.AUXILIARY_PREFIX + auxName;
+        String auxiliaryClassName = MockAuxiliaryCacheFactory.class.getName();
+        String eventLoggerClassName = MockCacheEventLogger.class.getName();
+        String auxiliaryAttributeClassName = MockAuxiliaryCacheAttributes.class.getName();
+
+        Properties props = new Properties();
+        props.put( auxPrefix, auxiliaryClassName );
+        props.put( auxPrefix + CompositeCacheConfigurator.ATTRIBUTE_PREFIX, auxiliaryAttributeClassName );
+        props.put( auxPrefix + AuxiliaryCacheConfigurator.CACHE_EVENT_LOGGER_PREFIX, eventLoggerClassName );
+
+//        System.out.print( props );
+
+        CompositeCacheManager manager = CompositeCacheManager.getUnconfiguredInstance();
+        CompositeCacheConfigurator configurator = new CompositeCacheConfigurator();
+
+        // DO WORK
+        AuxiliaryCache<String, String> aux = configurator.parseAuxiliary( props, manager, auxName, regionName );
+        MockAuxiliaryCache<String, String> result = (MockAuxiliaryCache<String, String>)aux;
+
+        // VERIFY
+        assertNotNull( "Should have an auxcache.", result );
+        assertNotNull( "Should have an event logger.", result.getCacheEventLogger() );
+    }
+
+    /**
+     * Verify that we can parse the spool chunk size
+     */
+    public void testParseSpoolChunkSize_Normal()
+    {
+        // SETUP
+        String regionName = "MyRegion";
+        int chunkSize = 5;
+
+        Properties props = new Properties();
+        props.put( "jcs.default", "" );
+        props.put( "jcs.default.cacheattributes.SpoolChunkSize", String.valueOf( chunkSize ) );
+
+        CompositeCacheManager manager = CompositeCacheManager.getUnconfiguredInstance();
+
+        // DO WORK
+        manager.configure( props );
+
+        // VERIFY
+        CompositeCache<String, String> cache = manager.getCache( regionName );
+        assertEquals( "Wrong chunkSize", cache.getCacheAttributes().getSpoolChunkSize(), chunkSize );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/CompositeCacheDiskUsageUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/CompositeCacheDiskUsageUnitTest.java
new file mode 100644
index 0000000..75a0bcf
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/CompositeCacheDiskUsageUnitTest.java
@@ -0,0 +1,511 @@
+package org.apache.commons.jcs3.engine.control;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.access.exception.CacheException;
+import org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCache;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCache;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.CacheStatus;
+import org.apache.commons.jcs3.engine.CompositeCacheAttributes;
+import org.apache.commons.jcs3.engine.ElementAttributes;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes;
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.engine.behavior.ICacheType.CacheType;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+import org.apache.commons.jcs3.engine.stats.behavior.IStats;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests of the disk usage settings for the CompositeCache.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class CompositeCacheDiskUsageUnitTest
+    extends TestCase
+{
+    private static final String CACHE_NAME = "testSpoolAllowed";
+
+    /**
+     * Test setup
+     */
+    @Override
+    public void setUp()
+    {
+        JCS.setConfigFilename( "/TestDiskCacheUsagePattern.ccf" );
+    }
+
+    /**
+     * Verify that the swap region is set to the correct pattern.
+     * <p>
+     * @throws CacheException
+     */
+    public void testSwapConfig()
+        throws CacheException
+    {
+        CacheAccess<String, String> swap = JCS.getInstance( "Swap" );
+        assertEquals( ICompositeCacheAttributes.DiskUsagePattern.SWAP, swap.getCacheAttributes()
+            .getDiskUsagePattern() );
+    }
+
+    /**
+     * Verify that the swap region is set to the correct pattern.
+     * <p>
+     * @throws CacheException
+     */
+    public void testUpdateConfig()
+        throws CacheException
+    {
+        CacheAccess<String, String> swap = JCS.getInstance( "Update" );
+        assertEquals( ICompositeCacheAttributes.DiskUsagePattern.UPDATE, swap.getCacheAttributes()
+            .getDiskUsagePattern() );
+    }
+
+    /**
+     * Setup a disk cache. Configure the disk usage pattern to swap. Call spool. Verify that the
+     * item is put to disk.
+     */
+    public void testSpoolAllowed()
+    {
+        // SETUP
+        ICompositeCacheAttributes cattr = new CompositeCacheAttributes();
+        cattr.setCacheName(CACHE_NAME);
+        cattr.setDiskUsagePattern( ICompositeCacheAttributes.DiskUsagePattern.SWAP );
+
+        IElementAttributes attr = new ElementAttributes();
+
+        CompositeCache<String, String> cache = new CompositeCache<>( cattr, attr );
+
+        MockAuxCache<String, String> mock = new MockAuxCache<>();
+        mock.cacheType = CacheType.DISK_CACHE;
+
+        @SuppressWarnings("unchecked")
+        AuxiliaryCache<String, String>[] auxArray = new AuxiliaryCache[] { mock };
+        cache.setAuxCaches( auxArray );
+
+        ICacheElement<String, String> inputElement = new CacheElement<>( CACHE_NAME, "key", "value" );
+
+        // DO WORK
+        cache.spoolToDisk( inputElement );
+
+        // VERIFY
+        assertEquals( "Wrong number of calls to the disk cache update.", 1, mock.updateCount );
+        assertEquals( "Wrong element updated.", inputElement, mock.lastUpdatedItem );
+    }
+
+    /**
+     * Setup a disk cache. Configure the disk usage pattern to not swap. Call spool. Verify that the
+     * item is not put to disk.
+     */
+    public void testSpoolNotAllowed()
+    {
+        // SETUP
+        ICompositeCacheAttributes cattr = new CompositeCacheAttributes();
+        cattr.setCacheName(CACHE_NAME);
+        cattr.setDiskUsagePattern( ICompositeCacheAttributes.DiskUsagePattern.UPDATE );
+
+        IElementAttributes attr = new ElementAttributes();
+
+        CompositeCache<String, String> cache = new CompositeCache<>( cattr, attr );
+
+        MockAuxCache<String, String> mock = new MockAuxCache<>();
+        mock.cacheType = CacheType.DISK_CACHE;
+
+        @SuppressWarnings("unchecked")
+        AuxiliaryCache<String, String>[] auxArray = new AuxiliaryCache[] { mock };
+        cache.setAuxCaches( auxArray );
+
+        ICacheElement<String, String> inputElement = new CacheElement<>( CACHE_NAME, "key", "value" );
+
+        // DO WORK
+        cache.spoolToDisk( inputElement );
+
+        // VERIFY
+        assertEquals( "Wrong number of calls to the disk cache update.", 0, mock.updateCount );
+    }
+
+    /**
+     * Setup a disk cache. Configure the disk usage pattern to UPDATE. Call updateAuxiliaries.
+     * Verify that the item is put to disk.
+     * <p>
+     * This tests that the items are put to disk on a normal put when the usage pattern is set
+     * appropriately.
+     * @throws IOException
+     */
+    public void testUpdateAllowed()
+        throws IOException
+    {
+        // SETUP
+        ICompositeCacheAttributes cattr = new CompositeCacheAttributes();
+        cattr.setCacheName(CACHE_NAME);
+        cattr.setDiskUsagePattern( ICompositeCacheAttributes.DiskUsagePattern.UPDATE );
+
+        IElementAttributes attr = new ElementAttributes();
+
+        CompositeCache<String, String> cache = new CompositeCache<>( cattr, attr );
+
+        MockAuxCache<String, String> mock = new MockAuxCache<>();
+        mock.cacheType = CacheType.DISK_CACHE;
+
+        @SuppressWarnings("unchecked")
+        AuxiliaryCache<String, String>[] auxArray = new AuxiliaryCache[] { mock };
+        cache.setAuxCaches( auxArray );
+
+        ICacheElement<String, String> inputElement = new CacheElement<>( CACHE_NAME, "key", "value" );
+
+        // DO WORK
+        cache.updateAuxiliaries( inputElement, true );
+
+        // VERIFY
+        assertEquals( "Wrong number of calls to the disk cache update.", 1, mock.updateCount );
+        assertEquals( "Wrong element updated.", inputElement, mock.lastUpdatedItem );
+    }
+
+    /**
+     * Setup a disk cache. Configure the disk usage pattern to UPDATE. Call updateAuxiliaries with
+     * local only set to false. Verify that the item is put to disk.
+     * <p>
+     * This tests that the items are put to disk on a normal put when the usage pattern is set
+     * appropriately. The local setting should have no impact on whether the item goes to disk.
+     * <p>
+     * @throws IOException
+     */
+    public void testUpdateAllowed_localFalse()
+        throws IOException
+    {
+        // SETUP
+        ICompositeCacheAttributes cattr = new CompositeCacheAttributes();
+        cattr.setCacheName(CACHE_NAME);
+        cattr.setDiskUsagePattern( ICompositeCacheAttributes.DiskUsagePattern.UPDATE );
+
+        IElementAttributes attr = new ElementAttributes();
+
+        CompositeCache<String, String> cache = new CompositeCache<>( cattr, attr );
+
+        MockAuxCache<String, String> mock = new MockAuxCache<>();
+        mock.cacheType = CacheType.DISK_CACHE;
+
+        @SuppressWarnings("unchecked")
+        AuxiliaryCache<String, String>[] auxArray = new AuxiliaryCache[] { mock };
+        cache.setAuxCaches( auxArray );
+
+        ICacheElement<String, String> inputElement = new CacheElement<>( CACHE_NAME, "key", "value" );
+
+        // DO WORK
+        cache.updateAuxiliaries( inputElement, false );
+
+        // VERIFY
+        assertEquals( "Wrong number of calls to the disk cache update.", 1, mock.updateCount );
+        assertEquals( "Wrong element updated.", inputElement, mock.lastUpdatedItem );
+    }
+
+    /**
+     * Setup a disk cache. Configure the disk usage pattern to SWAP. Call updateAuxiliaries. Verify
+     * that the item is not put to disk.
+     * <p>
+     * This tests that the items are not put to disk on a normal put when the usage pattern is set
+     * to SWAP.
+     * <p>
+     * @throws IOException
+     */
+    public void testUpdateNotAllowed()
+        throws IOException
+    {
+        // SETUP
+        ICompositeCacheAttributes cattr = new CompositeCacheAttributes();
+        cattr.setCacheName(CACHE_NAME);
+        cattr.setDiskUsagePattern( ICompositeCacheAttributes.DiskUsagePattern.SWAP );
+
+        IElementAttributes attr = new ElementAttributes();
+
+        CompositeCache<String, String> cache = new CompositeCache<>( cattr, attr );
+
+        MockAuxCache<String, String> mock = new MockAuxCache<>();
+        mock.cacheType = CacheType.DISK_CACHE;
+
+        @SuppressWarnings("unchecked")
+        AuxiliaryCache<String, String>[] auxArray = new AuxiliaryCache[] { mock };
+        cache.setAuxCaches( auxArray );
+
+        ICacheElement<String, String> inputElement = new CacheElement<>( CACHE_NAME, "key", "value" );
+
+        // DO WORK
+        cache.updateAuxiliaries( inputElement, true );
+
+        // VERIFY
+        assertEquals( "Wrong number of calls to the disk cache update.", 0, mock.updateCount );
+    }
+
+    /**
+     * Setup a disk cache. Configure the disk usage pattern to UPDATE. Call updateAuxiliaries.
+     * Verify that the item is put to disk.
+     * <p>
+     * This tests that the items are put to disk on a normal put when the usage pattern is set
+     * appropriately.
+     * @throws IOException
+     */
+    public void testUpdateAllowed_withOtherCaches()
+        throws IOException
+    {
+        // SETUP
+        ICompositeCacheAttributes cattr = new CompositeCacheAttributes();
+        cattr.setCacheName(CACHE_NAME);
+        cattr.setDiskUsagePattern( ICompositeCacheAttributes.DiskUsagePattern.UPDATE );
+
+        IElementAttributes attr = new ElementAttributes();
+
+        CompositeCache<String, String> cache = new CompositeCache<>( cattr, attr );
+
+        MockAuxCache<String, String> mock = new MockAuxCache<>();
+        mock.cacheType = CacheType.DISK_CACHE;
+
+        MockAuxCache<String, String> mockLateral = new MockAuxCache<>();
+        mockLateral.cacheType = CacheType.LATERAL_CACHE;
+
+        @SuppressWarnings("unchecked")
+        AuxiliaryCache<String, String>[] auxArray = new AuxiliaryCache[] { mock, mockLateral };
+        cache.setAuxCaches( auxArray );
+
+        ICacheElement<String, String> inputElement = new CacheElement<>( CACHE_NAME, "key", "value" );
+
+        // DO WORK
+        cache.updateAuxiliaries( inputElement, false );
+
+        // VERIFY
+        assertEquals( "Wrong number of calls to the disk cache update.", 1, mock.updateCount );
+        assertEquals( "Wrong element updated.", inputElement, mock.lastUpdatedItem );
+
+        assertEquals( "Wrong number of calls to the lateral cache update.", 1, mockLateral.updateCount );
+        assertEquals( "Wrong element updated with lateral.", inputElement, mockLateral.lastUpdatedItem );
+    }
+
+    /**
+     * Used to test the disk cache functionality.
+     * <p>
+     * @author Aaron Smuts
+     */
+    public static class MockAuxCache<K, V>
+        extends AbstractAuxiliaryCache<K, V>
+    {
+        /** The last item passed to update. */
+        public ICacheElement<K, V> lastUpdatedItem;
+
+        /** The number of times update was called. */
+        public int updateCount = 0;
+
+        /** The type that should be returned from getCacheType. */
+        public CacheType cacheType = CacheType.DISK_CACHE;
+
+        /** Resets counters and catchers. */
+        public void reset()
+        {
+            updateCount = 0;
+            lastUpdatedItem = null;
+        }
+
+        /**
+         * @param ce
+         * @throws IOException
+         */
+        @Override
+        public void update( ICacheElement<K, V> ce )
+            throws IOException
+        {
+            lastUpdatedItem = ce;
+            updateCount++;
+        }
+
+        /**
+         * @param key
+         * @return ICacheElement
+         * @throws IOException
+         */
+        @Override
+        public ICacheElement<K, V> get( K key )
+            throws IOException
+        {
+            return null;
+        }
+
+        /**
+         * Gets multiple items from the cache based on the given set of keys.
+         * <p>
+         * @param keys
+         * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is
+         *         no data in cache for any of these keys
+         */
+        @Override
+        public Map<K, ICacheElement<K, V>> getMultiple(Set<K> keys)
+        {
+            return new HashMap<>();
+        }
+
+        /**
+         * @param key
+         * @return false
+         * @throws IOException
+         */
+        @Override
+        public boolean remove( K key )
+            throws IOException
+        {
+            return false;
+        }
+
+        /** @throws IOException */
+        @Override
+        public void removeAll()
+            throws IOException
+        {
+            // noop
+        }
+
+        /** @throws IOException */
+        @Override
+        public void dispose()
+            throws IOException
+        {
+            // noop
+        }
+
+        /** @return 0 */
+        @Override
+        public int getSize()
+        {
+            return 0;
+        }
+
+        /** @return 0 */
+        @Override
+        public CacheStatus getStatus()
+        {
+            return CacheStatus.ALIVE;
+        }
+
+        /** @return null */
+        @Override
+        public String getCacheName()
+        {
+            return null;
+        }
+
+        /**
+         * @return null
+         * @throws IOException
+         */
+        @Override
+        public Set<K> getKeySet( )
+            throws IOException
+        {
+            return null;
+        }
+
+        /** @return null */
+        @Override
+        public IStats getStatistics()
+        {
+            return null;
+        }
+
+        /** @return null */
+        @Override
+        public String getStats()
+        {
+            return null;
+        }
+
+        /**
+         * Returns the setup cache type. This allows you to use this mock as multiple cache types.
+         * <p>
+         * @see org.apache.commons.jcs3.engine.behavior.ICacheType#getCacheType()
+         * @return cacheType
+         */
+        @Override
+        public CacheType getCacheType()
+        {
+            return cacheType;
+        }
+
+        /**
+         * @return Returns the AuxiliaryCacheAttributes.
+         */
+        @Override
+        public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
+        {
+            return null;
+        }
+
+        /**
+         * @param cacheEventLogger
+         */
+        @Override
+        public void setCacheEventLogger( ICacheEventLogger cacheEventLogger )
+        {
+            // TODO Auto-generated method stub
+
+        }
+
+        /**
+         * @param elementSerializer
+         */
+        @Override
+        public void setElementSerializer( IElementSerializer elementSerializer )
+        {
+            // TODO Auto-generated method stub
+
+        }
+
+        /** @return null */
+        @Override
+        public String getEventLoggingExtraInfo()
+        {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        /**
+         * @param pattern
+         * @return Collections.EMPTY_MAP;
+         * @throws IOException
+         */
+        @Override
+        public Map<K, ICacheElement<K, V>> getMatching(String pattern)
+            throws IOException
+        {
+            return Collections.emptyMap();
+        }
+
+
+    }
+
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/CompositeCacheManagerTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/CompositeCacheManagerTest.java
new file mode 100644
index 0000000..e9f144b
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/CompositeCacheManagerTest.java
@@ -0,0 +1,56 @@
+package org.apache.commons.jcs3.engine.control;
+
+import org.apache.commons.jcs3.engine.CacheStatus;
+import org.apache.commons.jcs3.engine.CompositeCacheAttributes;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+import org.apache.commons.jcs3.engine.control.CompositeCacheManager;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/** Unit tests for the composite cache manager */
+public class CompositeCacheManagerTest
+    extends TestCase
+{
+
+    /**
+     * Verify that calling release, when there are active clients, the caches are correctly disposed or not.
+     */
+    public void testRelease()
+    {
+        // See JCS-184
+        // create the manager
+        CompositeCacheManager manager = CompositeCacheManager.getInstance();
+        // add a simple cache
+        CompositeCacheAttributes cacheAttributes = new CompositeCacheAttributes();
+        CompositeCache<String, String> cache = new CompositeCache<>(cacheAttributes, /* attr */ null);
+        manager.addCache("simple_cache", cache);
+        // add a client to the cache
+        CompositeCacheManager.getUnconfiguredInstance();
+        // won't release as there are still clients. Only disposed when release() is called by
+        // the last client
+        manager.release();
+        assertEquals("The cache was disposed during release!", CacheStatus.ALIVE, cache.getStatus());
+        manager.release();
+        assertEquals("The cache was NOT disposed during release!", CacheStatus.DISPOSED, cache.getStatus());
+    }
+
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/CompositeCacheUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/CompositeCacheUnitTest.java
new file mode 100644
index 0000000..d9e2f5b
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/CompositeCacheUnitTest.java
@@ -0,0 +1,247 @@
+package org.apache.commons.jcs3.engine.control;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+import org.apache.commons.jcs3.auxiliary.MockAuxiliaryCache;
+import org.apache.commons.jcs3.engine.memory.MockMemoryCache;
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCache;
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.CompositeCacheAttributes;
+import org.apache.commons.jcs3.engine.ElementAttributes;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes;
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+import org.apache.commons.jcs3.engine.behavior.ICacheType.CacheType;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * Tests that directly engage the composite cache.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class CompositeCacheUnitTest
+    extends TestCase
+{
+    /**
+     * Verify that the freeMemoryElements method on the memory cache is called on shutdown if there
+     * is a disk cache.
+     * <p>
+     * @throws IOException
+     */
+    public void testShutdownMemoryFlush()
+        throws IOException
+    {
+        // SETUP
+        String cacheName = "testCacheName";
+        String mockMemoryCacheClassName = "org.apache.commons.jcs3.engine.memory.MockMemoryCache";
+        ICompositeCacheAttributes cattr = new CompositeCacheAttributes();
+        cattr.setMemoryCacheName( mockMemoryCacheClassName );
+
+        IElementAttributes attr = new ElementAttributes();
+
+        CompositeCache<String, Integer> cache = new CompositeCache<>( cattr, attr );
+
+        MockAuxiliaryCache<String, Integer> diskMock = new MockAuxiliaryCache<>();
+        diskMock.cacheType = CacheType.DISK_CACHE;
+        @SuppressWarnings("unchecked")
+        AuxiliaryCache<String, Integer>[] aux = new AuxiliaryCache[] { diskMock };
+        cache.setAuxCaches( aux );
+
+        // DO WORK
+        int numToInsert = 10;
+        for ( int i = 0; i < numToInsert; i++ )
+        {
+            ICacheElement<String, Integer> element = new CacheElement<>( cacheName, String.valueOf( i ), Integer.valueOf( i ) );
+            cache.update( element, false );
+        }
+
+        cache.dispose();
+
+        // VERIFY
+        MockMemoryCache<String, Integer> memoryCache = (MockMemoryCache<String, Integer>) cache.getMemoryCache();
+        assertEquals( "Wrong number freed.", numToInsert, memoryCache.lastNumberOfFreedElements );
+    }
+
+    /**
+     * Verify that the freeMemoryElements method on the memory cache is NOT called on shutdown if
+     * there is NOT a disk cache.
+     * <p>
+     * @throws IOException
+     */
+    public void testShutdownMemoryFlush_noDisk()
+        throws IOException
+    {
+        // SETUP
+        String cacheName = "testCacheName";
+        String mockMemoryCacheClassName = "org.apache.commons.jcs3.engine.memory.MockMemoryCache";
+        ICompositeCacheAttributes cattr = new CompositeCacheAttributes();
+        cattr.setMemoryCacheName( mockMemoryCacheClassName );
+
+        IElementAttributes attr = new ElementAttributes();
+
+        CompositeCache<String, Integer> cache = new CompositeCache<>( cattr, attr );
+
+        MockAuxiliaryCache<String, Integer> diskMock = new MockAuxiliaryCache<>();
+        diskMock.cacheType = CacheType.REMOTE_CACHE;
+        @SuppressWarnings("unchecked")
+        AuxiliaryCache<String, Integer>[] aux = new AuxiliaryCache[] { diskMock };
+        cache.setAuxCaches( aux );
+
+        // DO WORK
+        int numToInsert = 10;
+        for ( int i = 0; i < numToInsert; i++ )
+        {
+            ICacheElement<String, Integer> element = new CacheElement<>( cacheName, String.valueOf( i ), Integer.valueOf( i ) );
+            cache.update( element, false );
+        }
+
+        cache.dispose();
+
+        // VERIFY
+        MockMemoryCache<String, Integer> memoryCache = (MockMemoryCache<String, Integer>) cache.getMemoryCache();
+        assertEquals( "Wrong number freed.", 0, memoryCache.lastNumberOfFreedElements );
+    }
+
+    /**
+     * Verify we can get some matching elements..
+     * <p>
+     * @throws IOException
+     */
+    public void testGetMatching_Normal()
+        throws IOException
+    {
+        // SETUP
+        int maxMemorySize = 1000;
+        String keyprefix1 = "MyPrefix1";
+        String keyprefix2 = "MyPrefix2";
+        String cacheName = "testGetMatching_Normal";
+        String memoryCacheClassName = "org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache";
+        ICompositeCacheAttributes cattr = new CompositeCacheAttributes();
+        cattr.setMemoryCacheName( memoryCacheClassName );
+        cattr.setMaxObjects( maxMemorySize );
+
+        IElementAttributes attr = new ElementAttributes();
+
+        CompositeCache<String, Integer> cache = new CompositeCache<>( cattr, attr );
+
+        MockAuxiliaryCache<String, Integer> diskMock = new MockAuxiliaryCache<>();
+        diskMock.cacheType = CacheType.DISK_CACHE;
+        @SuppressWarnings("unchecked")
+        AuxiliaryCache<String, Integer>[] aux = new AuxiliaryCache[] { diskMock };
+        cache.setAuxCaches( aux );
+
+        // DO WORK
+        int numToInsertPrefix1 = 10;
+        // insert with prefix1
+        for ( int i = 0; i < numToInsertPrefix1; i++ )
+        {
+            ICacheElement<String, Integer> element = new CacheElement<>( cacheName, keyprefix1 + String.valueOf( i ), Integer.valueOf( i ) );
+            cache.update( element, false );
+        }
+
+        int numToInsertPrefix2 = 50;
+        // insert with prefix1
+        for ( int i = 0; i < numToInsertPrefix2; i++ )
+        {
+            ICacheElement<String, Integer> element = new CacheElement<>( cacheName, keyprefix2 + String.valueOf( i ), Integer.valueOf( i ) );
+            cache.update( element, false );
+        }
+
+        Map<?, ?> result1 = cache.getMatching( keyprefix1 + "\\S+" );
+        Map<?, ?> result2 = cache.getMatching( keyprefix2 + "\\S+" );
+
+        // VERIFY
+        assertEquals( "Wrong number returned 1:", numToInsertPrefix1, result1.size() );
+        assertEquals( "Wrong number returned 2:", numToInsertPrefix2, result2.size() );
+    }
+
+    /**
+     * Verify we try a disk aux on a getMatching call.
+     * <p>
+     * @throws IOException
+     */
+    public void testGetMatching_NotOnDisk()
+        throws IOException
+    {
+        // SETUP
+        int maxMemorySize = 0;
+        String cacheName = "testGetMatching_NotOnDisk";
+        String memoryCacheClassName = "org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache";
+        ICompositeCacheAttributes cattr = new CompositeCacheAttributes();
+        cattr.setCacheName(cacheName);
+        cattr.setMemoryCacheName( memoryCacheClassName );
+        cattr.setMaxObjects( maxMemorySize );
+
+        IElementAttributes attr = new ElementAttributes();
+
+        CompositeCache<String, Integer> cache = new CompositeCache<>( cattr, attr );
+
+        MockAuxiliaryCache<String, Integer> diskMock = new MockAuxiliaryCache<>();
+        diskMock.cacheType = CacheType.DISK_CACHE;
+        @SuppressWarnings("unchecked")
+        AuxiliaryCache<String, Integer>[] aux = new AuxiliaryCache[] { diskMock };
+        cache.setAuxCaches( aux );
+
+        // DO WORK
+        cache.getMatching( "junk" );
+
+        // VERIFY
+        assertEquals( "Wrong number of calls", 1, diskMock.getMatchingCallCount );
+    }
+
+    /**
+     * Verify we try a remote  aux on a getMatching call.
+     * <p>
+     * @throws IOException
+     */
+    public void testGetMatching_NotOnRemote()
+        throws IOException
+    {
+        // SETUP
+        int maxMemorySize = 0;
+        String cacheName = "testGetMatching_NotOnDisk";
+        String memoryCacheClassName = "org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache";
+        ICompositeCacheAttributes cattr = new CompositeCacheAttributes();
+        cattr.setCacheName(cacheName);
+        cattr.setMemoryCacheName( memoryCacheClassName );
+        cattr.setMaxObjects( maxMemorySize );
+
+        IElementAttributes attr = new ElementAttributes();
+
+        CompositeCache<String, Integer> cache = new CompositeCache<>( cattr, attr );
+
+        MockAuxiliaryCache<String, Integer> diskMock = new MockAuxiliaryCache<>();
+        diskMock.cacheType = CacheType.REMOTE_CACHE;
+        @SuppressWarnings("unchecked")
+        AuxiliaryCache<String, Integer>[] aux = new AuxiliaryCache[] { diskMock };
+        cache.setAuxCaches( aux );
+
+        // DO WORK
+        cache.getMatching( "junk" );
+
+        // VERIFY
+        assertEquals( "Wrong number of calls", 1, diskMock.getMatchingCallCount );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/MockCompositeCacheManager.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/MockCompositeCacheManager.java
new file mode 100644
index 0000000..18a025e
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/MockCompositeCacheManager.java
@@ -0,0 +1,127 @@
+package org.apache.commons.jcs3.engine.control;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Properties;
+
+import org.apache.commons.jcs3.auxiliary.AuxiliaryCache;
+import org.apache.commons.jcs3.engine.CompositeCacheAttributes;
+import org.apache.commons.jcs3.engine.ElementAttributes;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
+import org.apache.commons.jcs3.engine.behavior.IShutdownObserver;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+
+/** For testing. */
+public class MockCompositeCacheManager
+    implements ICompositeCacheManager
+{
+    /** The cache that was returned. */
+    private CompositeCache<?, ?> cache;
+
+    /** Properties with which this manager was configured. This is exposed for other managers. */
+    private Properties configurationProperties;
+
+    /**
+     * @param cacheName
+     * @return Returns a CompositeCache
+     */
+    @Override
+    @SuppressWarnings("unchecked")
+    public <K, V> CompositeCache<K, V> getCache( String cacheName )
+    {
+        if ( cache == null )
+        {
+//            System.out.println( "Creating mock cache" );
+            CompositeCache<K, V> newCache =
+                new CompositeCache<>( new CompositeCacheAttributes(), new ElementAttributes() );
+            this.setCache( newCache );
+        }
+
+        return (CompositeCache<K, V>)cache;
+    }
+
+    @Override
+    public <K, V> AuxiliaryCache<K, V> getAuxiliaryCache(String auxName, String cacheName)
+    {
+        return null;
+    }
+
+    /**
+     * @param cache The cache to set.
+     */
+    public void setCache( CompositeCache<?, ?> cache )
+    {
+        this.cache = cache;
+    }
+
+    /**
+     * @return Returns the cache.
+     */
+    public CompositeCache<?, ?> getCache()
+    {
+        return cache;
+    }
+
+    /**
+     * This is exposed so other manager can get access to the props.
+     * <p>
+     * @param props
+     */
+    public void setConfigurationProperties( Properties props )
+    {
+        this.configurationProperties = props;
+    }
+
+    /**
+     * This is exposed so other manager can get access to the props.
+     * <p>
+     * @return the configurationProperties
+     */
+    @Override
+    public Properties getConfigurationProperties()
+    {
+        return configurationProperties;
+    }
+
+    /** @return Mock */
+    @Override
+    public String getStats()
+    {
+        return "Mock";
+    }
+
+	/**
+	 * @see org.apache.commons.jcs3.engine.behavior.IShutdownObservable#registerShutdownObserver(org.apache.commons.jcs3.engine.behavior.IShutdownObserver)
+	 */
+	@Override
+	public void registerShutdownObserver(IShutdownObserver observer)
+	{
+		// Do nothing
+	}
+
+	/**
+	 * @see org.apache.commons.jcs3.engine.behavior.IShutdownObservable#deregisterShutdownObserver(org.apache.commons.jcs3.engine.behavior.IShutdownObserver)
+	 */
+	@Override
+	public void deregisterShutdownObserver(IShutdownObserver observer)
+	{
+		// Do nothing
+	}
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/MockElementSerializer.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/MockElementSerializer.java
new file mode 100644
index 0000000..7cc78b0
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/MockElementSerializer.java
@@ -0,0 +1,87 @@
+package org.apache.commons.jcs3.engine.control;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.utils.serialization.StandardSerializer;
+
+/** For mocking. */
+public class MockElementSerializer
+    implements IElementSerializer
+{
+    /** test property */
+    private String testProperty;
+
+    /** What's used in the background */
+    private final StandardSerializer serializer = new StandardSerializer();
+
+    /** times out was called */
+    public int deSerializeCount = 0;
+
+    /** times in was called */
+    public int serializeCount = 0;
+
+    /**
+     * @param bytes
+     * @return Object
+     * @throws IOException
+     * @throws ClassNotFoundException
+     *
+     */
+    @Override
+    public <T> T deSerialize( byte[] bytes, ClassLoader loader )
+        throws IOException, ClassNotFoundException
+    {
+        deSerializeCount++;
+        return serializer.deSerialize( bytes, loader );
+    }
+
+    /**
+     * @param obj
+     * @return byte[]
+     * @throws IOException
+     *
+     */
+    @Override
+    public <T> byte[] serialize( T obj )
+        throws IOException
+    {
+        serializeCount++;
+        return serializer.serialize( obj );
+    }
+
+    /**
+     * @param testProperty
+     */
+    public void setTestProperty( String testProperty )
+    {
+        this.testProperty = testProperty;
+    }
+
+    /**
+     * @return testProperty
+     */
+    public String getTestProperty()
+    {
+        return testProperty;
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/event/ElementEventHandlerMockImpl.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/event/ElementEventHandlerMockImpl.java
new file mode 100644
index 0000000..07dc0fe
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/event/ElementEventHandlerMockImpl.java
@@ -0,0 +1,186 @@
+package org.apache.commons.jcs3.engine.control.event;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.engine.control.event.behavior.ElementEventType;
+import org.apache.commons.jcs3.engine.control.event.behavior.IElementEvent;
+import org.apache.commons.jcs3.engine.control.event.behavior.IElementEventHandler;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/**
+ * @author aaronsm
+ */
+public class ElementEventHandlerMockImpl
+    implements IElementEventHandler
+{
+    /** Times called. */
+    private int callCount = 0;
+
+    /** The logger */
+    private static final Log log = LogManager.getLog( ElementEventHandlerMockImpl.class );
+
+    /** ELEMENT_EVENT_SPOOLED_DISK_AVAILABLE */
+    private int spoolCount = 0;
+
+    /** ELEMENT_EVENT_SPOOLED_NOT_ALLOWED */
+    private int spoolNotAllowedCount = 0;
+
+    /** ELEMENT_EVENT_SPOOLED_DISK_NOT_AVAILABLE */
+    private int spoolNoDiskCount = 0;
+
+    /** ELEMENT_EVENT_EXCEEDED_MAXLIFE_BACKGROUND */
+    private int exceededMaxLifeBackgroundCount = 0;
+
+    /** ELEMENT_EVENT_EXCEEDED_IDLETIME_BACKGROUND */
+    private int exceededIdleTimeBackgroundCount = 0;
+
+    /**
+     * @param event
+     */
+    @Override
+    public synchronized <T> void handleElementEvent( IElementEvent<T> event )
+    {
+        setCallCount( getCallCount() + 1 );
+
+        if ( log.isDebugEnabled() )
+        {
+            log.debug( "HANDLER -- HANDLER -- HANDLER -- ---EVENT CODE = " + event.getElementEvent() );
+            log.debug( "/n/n EVENT CODE = " + event.getElementEvent() + " ***************************" );
+        }
+
+        if ( event.getElementEvent() == ElementEventType.SPOOLED_DISK_AVAILABLE )
+        {
+            setSpoolCount( getSpoolCount() + 1 );
+        }
+        else if ( event.getElementEvent() == ElementEventType.SPOOLED_NOT_ALLOWED )
+        {
+            setSpoolNotAllowedCount( getSpoolNotAllowedCount() + 1 );
+        }
+        else if ( event.getElementEvent() == ElementEventType.SPOOLED_DISK_NOT_AVAILABLE )
+        {
+            setSpoolNoDiskCount( getSpoolNoDiskCount() + 1 );
+        }
+        else if ( event.getElementEvent() == ElementEventType.EXCEEDED_MAXLIFE_BACKGROUND )
+        {
+            setExceededMaxLifeBackgroundCount( getExceededMaxLifeBackgroundCount() + 1 );
+        }
+        else if ( event.getElementEvent() == ElementEventType.EXCEEDED_IDLETIME_BACKGROUND )
+        {
+            setExceededIdleTimeBackgroundCount( getExceededIdleTimeBackgroundCount() + 1 );
+        }
+    }
+
+    /**
+     * @param spoolCount The spoolCount to set.
+     */
+    public void setSpoolCount( int spoolCount )
+    {
+        this.spoolCount = spoolCount;
+    }
+
+    /**
+     * @return Returns the spoolCount.
+     */
+    public int getSpoolCount()
+    {
+        return spoolCount;
+    }
+
+    /**
+     * @param spoolNotAllowedCount The spoolNotAllowedCount to set.
+     */
+    public void setSpoolNotAllowedCount( int spoolNotAllowedCount )
+    {
+        this.spoolNotAllowedCount = spoolNotAllowedCount;
+    }
+
+    /**
+     * @return Returns the spoolNotAllowedCount.
+     */
+    public int getSpoolNotAllowedCount()
+    {
+        return spoolNotAllowedCount;
+    }
+
+    /**
+     * @param spoolNoDiskCount The spoolNoDiskCount to set.
+     */
+    public void setSpoolNoDiskCount( int spoolNoDiskCount )
+    {
+        this.spoolNoDiskCount = spoolNoDiskCount;
+    }
+
+    /**
+     * @return Returns the spoolNoDiskCount.
+     */
+    public int getSpoolNoDiskCount()
+    {
+        return spoolNoDiskCount;
+    }
+
+    /**
+     * @param exceededMaxLifeBackground The exceededMaxLifeBackground to set.
+     */
+    public void setExceededMaxLifeBackgroundCount( int exceededMaxLifeBackground )
+    {
+        this.exceededMaxLifeBackgroundCount = exceededMaxLifeBackground;
+    }
+
+    /**
+     * @return Returns the exceededMaxLifeBackground.
+     */
+    public int getExceededMaxLifeBackgroundCount()
+    {
+        return exceededMaxLifeBackgroundCount;
+    }
+
+    /**
+     * @param callCount The callCount to set.
+     */
+    public void setCallCount( int callCount )
+    {
+        this.callCount = callCount;
+    }
+
+    /**
+     * @return Returns the callCount.
+     */
+    public int getCallCount()
+    {
+        return callCount;
+    }
+
+    /**
+     * @param exceededIdleTimeBackground The exceededIdleTimeBackground to set.
+     */
+    public void setExceededIdleTimeBackgroundCount( int exceededIdleTimeBackground )
+    {
+        this.exceededIdleTimeBackgroundCount = exceededIdleTimeBackground;
+    }
+
+    /**
+     * @return Returns the exceededIdleTimeBackground.
+     */
+    public int getExceededIdleTimeBackgroundCount()
+    {
+        return exceededIdleTimeBackgroundCount;
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/event/SimpleEventHandlingUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/event/SimpleEventHandlingUnitTest.java
new file mode 100644
index 0000000..7ac78ae
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/event/SimpleEventHandlingUnitTest.java
@@ -0,0 +1,380 @@
+package org.apache.commons.jcs3.engine.control.event;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.engine.ElementAttributes;
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+import org.apache.commons.jcs3.engine.control.event.behavior.IElementEvent;
+import org.apache.commons.jcs3.engine.control.event.behavior.IElementEventHandler;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/**
+ * This test suite verifies that the basic ElementEvent are called as they should be.
+ */
+public class SimpleEventHandlingUnitTest
+    extends TestCase
+{
+    /** Items to test with */
+    private static int items = 20000;
+
+    /**
+     * Test setup with expected configuration parameters.
+     */
+    @Override
+    public void setUp()
+    {
+        JCS.setConfigFilename( "/TestSimpleEventHandling.ccf" );
+    }
+
+    /**
+     * Verify that the spooled event is called as expected.
+     * <p>
+     * @throws Exception Description of the Exception
+     */
+    public void testSpoolEvent()
+        throws Exception
+    {
+        // SETUP
+        MyEventHandler meh = new MyEventHandler();
+
+        CacheAccess<String, String> jcs = JCS.getInstance( "WithDisk" );
+        // this should add the event handler to all items as they are created.
+        IElementAttributes attributes = jcs.getDefaultElementAttributes();
+        attributes.addElementEventHandler( meh );
+        jcs.setDefaultElementAttributes( attributes );
+
+        // DO WORK
+        // put them in
+        for ( int i = 0; i <= items; i++ )
+        {
+            jcs.put( i + ":key", "data" + i );
+        }
+
+        // wait a bit for it to finish
+        Thread.sleep( items / 20 );
+
+        // VERIFY
+        // test to see if the count is right
+        assertTrue( "The number of ELEMENT_EVENT_SPOOLED_DISK_AVAILABLE events [" + meh.getSpoolCount()
+            + "] does not equal the number expected [" + items + "]", meh.getSpoolCount() >= items );
+    }
+
+    /**
+     * Test overflow with no disk configured for the region.
+     * <p>
+     * @throws Exception
+     */
+    public void testSpoolNoDiskEvent()
+        throws Exception
+    {
+        CacheAccess<String, String> jcs = JCS.getInstance( "NoDisk" );
+
+        MyEventHandler meh = new MyEventHandler();
+
+        // this should add the event handler to all items as they are created.
+        IElementAttributes attributes = jcs.getDefaultElementAttributes();
+        attributes.addElementEventHandler( meh );
+        jcs.setDefaultElementAttributes( attributes );
+
+        // put them in
+        for ( int i = 0; i <= items; i++ )
+        {
+            jcs.put( i + ":key", "data" + i );
+        }
+
+        // wait a bit for it to finish
+        Thread.sleep( items / 20 );
+
+        // test to see if the count is right
+        assertTrue( "The number of ELEMENT_EVENT_SPOOLED_DISK_NOT_AVAILABLE events  [" + meh.getSpoolNoDiskCount()
+            + "] does not equal the number expected.", meh.getSpoolNoDiskCount() >= items );
+
+    }
+
+    /**
+     * Test the ELEMENT_EVENT_SPOOLED_NOT_ALLOWED event.
+     * @throws Exception
+     */
+    public void testSpoolNotAllowedEvent()
+        throws Exception
+    {
+        MyEventHandler meh = new MyEventHandler();
+
+        CacheAccess<String, String> jcs = JCS.getInstance( "DiskButNotAllowed" );
+        // this should add the event handler to all items as they are created.
+        IElementAttributes attributes = jcs.getDefaultElementAttributes();
+        attributes.addElementEventHandler( meh );
+        jcs.setDefaultElementAttributes( attributes );
+
+        // put them in
+        for ( int i = 0; i <= items; i++ )
+        {
+            jcs.put( i + ":key", "data" + i );
+        }
+
+        // wait a bit for it to finish
+        Thread.sleep( items / 20 );
+
+        // test to see if the count is right
+        assertTrue( "The number of ELEMENT_EVENT_SPOOLED_NOT_ALLOWED events [" + meh.getSpoolNotAllowedCount()
+            + "] does not equal the number expected.", meh.getSpoolNotAllowedCount() >= items );
+
+    }
+
+    /**
+     * Test the ELEMENT_EVENT_SPOOLED_NOT_ALLOWED event.
+     * @throws Exception
+     */
+    public void testSpoolNotAllowedEventOnItem()
+        throws Exception
+    {
+        MyEventHandler meh = new MyEventHandler();
+
+        CacheAccess<String, String> jcs = JCS.getInstance( "DiskButNotAllowed" );
+        // this should add the event handler to all items as they are created.
+        //IElementAttributes attributes = jcs.getDefaultElementAttributes();
+        //attributes.addElementEventHandler( meh );
+        //jcs.setDefaultElementAttributes( attributes );
+
+        // put them in
+        for ( int i = 0; i <= items; i++ )
+        {
+            IElementAttributes attributes = jcs.getDefaultElementAttributes();
+            attributes.addElementEventHandler( meh );
+            jcs.put( i + ":key", "data" + i, attributes );
+        }
+
+        // wait a bit for it to finish
+        Thread.sleep( items / 20 );
+
+        // test to see if the count is right
+        assertTrue( "The number of ELEMENT_EVENT_SPOOLED_NOT_ALLOWED events [" + meh.getSpoolNotAllowedCount()
+            + "] does not equal the number expected.", meh.getSpoolNotAllowedCount() >= items );
+
+    }
+
+    /**
+     * Test the ELEMENT_EVENT_EXCEEDED_MAXLIFE_ONREQUEST event.
+     * @throws Exception
+     */
+    public void testExceededMaxlifeOnrequestEvent()
+        throws Exception
+    {
+        MyEventHandler meh = new MyEventHandler();
+
+        CacheAccess<String, String> jcs = JCS.getInstance( "Maxlife" );
+        // this should add the event handler to all items as they are created.
+        IElementAttributes attributes = jcs.getDefaultElementAttributes();
+        attributes.addElementEventHandler( meh );
+        jcs.setDefaultElementAttributes( attributes );
+
+        // put them in
+        for ( int i = 0; i < 200; i++ )
+        {
+            jcs.put( i + ":key", "data" + i);
+        }
+
+        // wait a bit for the items to expire
+        Thread.sleep( 3000 );
+
+        for ( int i = 0; i < 200; i++ )
+        {
+            String value = jcs.get( i + ":key");
+            assertNull("Item should be null for key " + i + ":key, but is " + value, value);
+        }
+
+        // wait a bit for it to finish
+        Thread.sleep( 100 );
+
+        // test to see if the count is right
+        assertTrue( "The number of ELEMENT_EVENT_EXCEEDED_MAXLIFE_ONREQUEST events [" + meh.getExceededMaxlifeCount()
+            + "] does not equal the number expected.", meh.getExceededMaxlifeCount() >= 200 );
+    }
+
+    /**
+     * Test the ELEMENT_EVENT_EXCEEDED_IDLETIME_ONREQUEST event.
+     * @throws Exception
+     */
+    public void testExceededIdletimeOnrequestEvent()
+        throws Exception
+    {
+        MyEventHandler meh = new MyEventHandler();
+
+        CacheAccess<String, String> jcs = JCS.getInstance( "Idletime" );
+        // this should add the event handler to all items as they are created.
+        IElementAttributes attributes = jcs.getDefaultElementAttributes();
+        attributes.addElementEventHandler( meh );
+        jcs.setDefaultElementAttributes( attributes );
+
+        // put them in
+        for ( int i = 0; i < 200; i++ )
+        {
+            jcs.put( i + ":key", "data" + i);
+        }
+
+        // update access time
+        for ( int i = 0; i < 200; i++ )
+        {
+            String value = jcs.get( i + ":key");
+            assertNotNull("Item should not be null for key " + i + ":key", value);
+        }
+
+        // wait a bit for the items to expire
+        Thread.sleep( 1500 );
+
+        for ( int i = 0; i < 200; i++ )
+        {
+            String value = jcs.get( i + ":key");
+            assertNull("Item should be null for key " + i + ":key, but is " + value, value);
+        }
+
+        // wait a bit for it to finish
+        Thread.sleep( 100 );
+
+        // test to see if the count is right
+        assertTrue( "The number of ELEMENT_EVENT_EXCEEDED_IDLETIME_ONREQUEST events [" + meh.getExceededIdletimeCount()
+            + "] does not equal the number expected.", meh.getExceededIdletimeCount() >= 200 );
+    }
+
+    /**
+     * Test that cloned ElementAttributes have different creation times.
+     * @throws Exception
+     */
+    public void testElementAttributesCreationTime()
+        throws Exception
+    {
+    	ElementAttributes elem1 = new ElementAttributes();
+    	long ctime1 = elem1.getCreateTime();
+
+    	Thread.sleep(10);
+
+    	IElementAttributes elem2 = elem1.clone();
+    	long ctime2 = elem2.getCreateTime();
+
+    	assertFalse("Creation times should be different", ctime1 == ctime2);
+    }
+
+    /**
+     * Simple event counter used to verify test results.
+     */
+    public static class MyEventHandler
+        implements IElementEventHandler
+    {
+        /** times spool called */
+        private int spoolCount = 0;
+
+        /** times spool not allowed */
+        private int spoolNotAllowedCount = 0;
+
+        /** times spool without disk */
+        private int spoolNoDiskCount = 0;
+
+        /** times exceeded maxlife */
+        private int exceededMaxlifeCount = 0;
+
+        /** times exceeded idle time */
+        private int exceededIdletimeCount = 0;
+
+        /**
+         * @param event
+         */
+        @Override
+        public synchronized <T> void handleElementEvent( IElementEvent<T> event )
+        {
+            //System.out.println( "Handling Event of Type " +
+            // event.getElementEvent() );
+
+            switch (event.getElementEvent())
+            {
+                case SPOOLED_DISK_AVAILABLE:
+                //System.out.println( "Handling Event of Type
+                // ELEMENT_EVENT_SPOOLED_DISK_AVAILABLE, " + getSpoolCount() );
+                spoolCount++;
+                break;
+
+                case SPOOLED_NOT_ALLOWED:
+                spoolNotAllowedCount++;
+                break;
+
+                case SPOOLED_DISK_NOT_AVAILABLE:
+                spoolNoDiskCount++;
+                break;
+
+                case EXCEEDED_MAXLIFE_ONREQUEST:
+                exceededMaxlifeCount++;
+                break;
+
+                case EXCEEDED_IDLETIME_ONREQUEST:
+                exceededIdletimeCount++;
+                break;
+
+                case EXCEEDED_IDLETIME_BACKGROUND:
+                break;
+
+                case EXCEEDED_MAXLIFE_BACKGROUND:
+                break;
+            }
+        }
+
+        /**
+         * @return Returns the spoolCount.
+         */
+        protected int getSpoolCount()
+        {
+            return spoolCount;
+        }
+
+        /**
+         * @return Returns the spoolNotAllowedCount.
+         */
+        protected int getSpoolNotAllowedCount()
+        {
+            return spoolNotAllowedCount;
+        }
+
+        /**
+         * @return Returns the spoolNoDiskCount.
+         */
+        protected int getSpoolNoDiskCount()
+        {
+            return spoolNoDiskCount;
+        }
+
+        /**
+         * @return the exceededMaxlifeCount
+         */
+        protected int getExceededMaxlifeCount()
+        {
+            return exceededMaxlifeCount;
+        }
+
+        /**
+         * @return the exceededIdletimeCount
+         */
+        protected int getExceededIdletimeCount()
+        {
+            return exceededIdletimeCount;
+        }
+    }
+
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/logging/CacheEventLoggerDebugLoggerUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/logging/CacheEventLoggerDebugLoggerUnitTest.java
new file mode 100644
index 0000000..a4d80de
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/logging/CacheEventLoggerDebugLoggerUnitTest.java
@@ -0,0 +1,118 @@
+package org.apache.commons.jcs3.engine.logging;
+
+import java.io.StringWriter;
+
+import org.apache.commons.jcs3.TestLogConfigurationUtil;
+import org.apache.commons.jcs3.engine.logging.CacheEventLoggerDebugLogger;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEvent;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/** Unit tests for the debug implementation */
+public class CacheEventLoggerDebugLoggerUnitTest
+    extends TestCase
+{
+
+    /** verify that we can log */
+    public void testLogICacheEvent_normal()
+    {
+        // SETUP
+        String logCategoryName = "testLogEvent_normal";
+
+        String source = "mySource";
+        String region = "my region";
+        String eventName = "MyEventName";
+        String optionalDetails = "SomeExtraData";
+        String key = "my key";
+
+        StringWriter stringWriter = new StringWriter();
+        TestLogConfigurationUtil.configureLogger( stringWriter, logCategoryName );
+
+        CacheEventLoggerDebugLogger logger = new CacheEventLoggerDebugLogger();
+        logger.setLogCategoryName( logCategoryName );
+
+        ICacheEvent<String> event = logger.createICacheEvent( source, region, eventName, optionalDetails, key );
+
+        // DO WORK
+        logger.logICacheEvent( event );
+
+        // VERIFY
+        String result = stringWriter.toString();
+        assertTrue( "An event with the source should have been logged:" + result, result.indexOf( source ) != -1 );
+        assertTrue( "An event with the region should have been logged:" + result, result.indexOf( region ) != -1 );
+        assertTrue( "An event with the event name should have been logged:" + result, result.indexOf( eventName ) != -1 );
+        assertTrue( "An event with the optionalDetails should have been logged:" + result, result.indexOf( optionalDetails ) != -1 );
+        assertTrue( "An event with the key should have been logged:" + result, result.indexOf( key ) != -1 );
+    }
+
+    /** verify that we can log */
+    public void testLogApplicationEvent_normal()
+    {
+        // SETUP
+        String logCategoryName = "testLogApplicationEvent_normal";
+
+        String source = "mySource";
+        String eventName = "MyEventName";
+        String optionalDetails = "SomeExtraData";
+
+        StringWriter stringWriter = new StringWriter();
+        TestLogConfigurationUtil.configureLogger( stringWriter, logCategoryName );
+
+        CacheEventLoggerDebugLogger logger = new CacheEventLoggerDebugLogger();
+        logger.setLogCategoryName( logCategoryName );
+
+        // DO WORK
+        logger.logApplicationEvent( source, eventName, optionalDetails );
+
+        // VERIFY
+        String result = stringWriter.toString();
+        assertTrue( "An event with the source should have been logged:" + result, result.indexOf( source ) != -1 );
+        assertTrue( "An event with the event name should have been logged:" + result, result.indexOf( eventName ) != -1 );
+        assertTrue( "An event with the optionalDetails should have been logged:" + result, result.indexOf( optionalDetails ) != -1 );
+    }
+
+    /** verify that we can log */
+    public void testLogError_normal()
+    {
+        // SETUP
+        String logCategoryName = "testLogApplicationEvent_normal";
+
+        String source = "mySource";
+        String eventName = "MyEventName";
+        String errorMessage = "SomeExtraData";
+
+        StringWriter stringWriter = new StringWriter();
+        TestLogConfigurationUtil.configureLogger( stringWriter, logCategoryName );
+
+        CacheEventLoggerDebugLogger logger = new CacheEventLoggerDebugLogger();
+        logger.setLogCategoryName( logCategoryName );
+
+        // DO WORK
+        logger.logError( source, eventName, errorMessage );
+
+        // VERIFY
+        String result = stringWriter.toString();
+        assertTrue( "An event with the source should have been logged:" + result, result.indexOf( source ) != -1 );
+        assertTrue( "An event with the event name should have been logged:" + result, result.indexOf( eventName ) != -1 );
+        assertTrue( "An event with the errorMessage should have been logged:" + result, result.indexOf( errorMessage ) != -1 );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/logging/MockCacheEventLogger.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/logging/MockCacheEventLogger.java
new file mode 100644
index 0000000..036bcdb
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/logging/MockCacheEventLogger.java
@@ -0,0 +1,96 @@
+package org.apache.commons.jcs3.engine.logging;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.jcs3.engine.logging.CacheEvent;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEvent;
+import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
+
+/**
+ * For testing the configurator.
+ */
+public class MockCacheEventLogger
+    implements ICacheEventLogger
+{
+    /** test property */
+    private String testProperty;
+
+    /**
+     * @param source
+     * @param eventName
+     * @param optionalDetails
+     */
+    @Override
+    public void logApplicationEvent( String source, String eventName, String optionalDetails )
+    {
+        // TODO Auto-generated method stub
+    }
+
+    /**
+     * @param source
+     * @param eventName
+     * @param errorMessage
+     */
+    @Override
+    public void logError( String source, String eventName, String errorMessage )
+    {
+        // TODO Auto-generated method stub
+    }
+
+    /**
+     * @param source
+     * @param region
+     * @param eventName
+     * @param optionalDetails
+     * @param key
+     * @return ICacheEvent
+     */
+    @Override
+    public <T> ICacheEvent<T> createICacheEvent( String source, String region, String eventName, String optionalDetails,
+                                          T key )
+    {
+        return new CacheEvent<>();
+    }
+
+    /**
+     * @param event
+     */
+    @Override
+    public <T> void logICacheEvent( ICacheEvent<T> event )
+    {
+        // TODO Auto-generated method stub
+    }
+
+    /**
+     * @param testProperty
+     */
+    public void setTestProperty( String testProperty )
+    {
+        this.testProperty = testProperty;
+    }
+
+    /**
+     * @return testProperty
+     */
+    public String getTestProperty()
+    {
+        return testProperty;
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/match/KeyMatcherPatternImpllUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/match/KeyMatcherPatternImpllUnitTest.java
new file mode 100644
index 0000000..eea1518
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/match/KeyMatcherPatternImpllUnitTest.java
@@ -0,0 +1,120 @@
+package org.apache.commons.jcs3.engine.match;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.commons.jcs3.engine.match.KeyMatcherPatternImpl;
+
+/** Unit tests for the key matcher. */
+public class KeyMatcherPatternImpllUnitTest
+    extends TestCase
+{
+    /**
+     * Verify that the matching method works.
+     */
+    public void testGetMatchingKeysFromArray_AllMatch()
+    {
+        // SETUP
+        int numToInsertPrefix1 = 10;
+        Set<String> keyArray = new HashSet<>();
+
+        String keyprefix1 = "MyPrefixC";
+
+        // insert with prefix1
+        for ( int i = 0; i < numToInsertPrefix1; i++ )
+        {
+            keyArray.add(keyprefix1 + String.valueOf( i ));
+        }
+
+        KeyMatcherPatternImpl<String> keyMatcher = new KeyMatcherPatternImpl<>();
+
+        // DO WORK
+        Set<String> result1 = keyMatcher.getMatchingKeysFromArray( keyprefix1 + ".", keyArray );
+
+        // VERIFY
+        assertEquals( "Wrong number returned 1: " + result1, numToInsertPrefix1, result1.size() );
+    }
+
+    /**
+     * Verify that the matching method works.
+     */
+    public void testGetMatchingKeysFromArray_AllMatchFirstNull()
+    {
+        // SETUP
+        int numToInsertPrefix1 = 10;
+        Set<String> keyArray = new HashSet<>();
+
+        String keyprefix1 = "MyPrefixC";
+
+        // insert with prefix1
+        for ( int i = 1; i < numToInsertPrefix1 + 1; i++ )
+        {
+            keyArray.add(keyprefix1 + String.valueOf( i ));
+        }
+
+        KeyMatcherPatternImpl<String> keyMatcher = new KeyMatcherPatternImpl<>();
+
+        // DO WORK
+        Set<String> result1 = keyMatcher.getMatchingKeysFromArray( keyprefix1 + "\\S+", keyArray );
+
+        // VERIFY
+        assertEquals( "Wrong number returned 1: " + result1, numToInsertPrefix1, result1.size() );
+    }
+
+    /**
+     * Verify that the matching method works.
+     */
+    public void testGetMatchingKeysFromArray_TwoTypes()
+    {
+        // SETUP
+        int numToInsertPrefix1 = 10;
+        int numToInsertPrefix2 = 50;
+        Set<String> keyArray = new HashSet<>();
+
+        String keyprefix1 = "MyPrefixA";
+        String keyprefix2 = "MyPrefixB";
+
+        // insert with prefix1
+        for ( int i = 0; i < numToInsertPrefix1; i++ )
+        {
+            keyArray.add(keyprefix1 + String.valueOf( i ));
+        }
+
+        // insert with prefix2
+        for ( int i = numToInsertPrefix1; i < numToInsertPrefix2 + numToInsertPrefix1; i++ )
+        {
+            keyArray.add(keyprefix2 + String.valueOf( i ));
+        }
+
+        KeyMatcherPatternImpl<String> keyMatcher = new KeyMatcherPatternImpl<>();
+
+        // DO WORK
+        Set<String> result1 = keyMatcher.getMatchingKeysFromArray( keyprefix1 + ".+", keyArray );
+        Set<String> result2 = keyMatcher.getMatchingKeysFromArray( keyprefix2 + ".+", keyArray );
+
+        // VERIFY
+        assertEquals( "Wrong number returned 1: " + result1, numToInsertPrefix1, result1.size() );
+        assertEquals( "Wrong number returned 2: " + result2, numToInsertPrefix2, result2.size() );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/memory/MockMemoryCache.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/memory/MockMemoryCache.java
new file mode 100644
index 0000000..29e7f9d
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/memory/MockMemoryCache.java
@@ -0,0 +1,255 @@
+package org.apache.commons.jcs3.engine.memory;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+import org.apache.commons.jcs3.engine.memory.behavior.IMemoryCache;
+import org.apache.commons.jcs3.engine.stats.behavior.IStats;
+
+/**
+ * Mock implementation of a memory cache for testing things like the memory shrinker.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class MockMemoryCache<K, V>
+    implements IMemoryCache<K, V>
+{
+    /** Config */
+    private ICompositeCacheAttributes cacheAttr;
+
+    /** Internal map */
+    private final HashMap<K, ICacheElement<K, V>> map = new HashMap<>();
+
+    /** The number of times waterfall was called. */
+    public int waterfallCallCount = 0;
+
+    /** The number passed to the last call of free elements. */
+    public int lastNumberOfFreedElements = 0;
+
+    /**
+     * Does nothing
+     * @param cache
+     */
+    @Override
+    public void initialize( CompositeCache<K, V> cache )
+    {
+        // nothing
+    }
+
+    /**
+     * Destroy the memory cache
+     * <p>
+     * @throws IOException
+     */
+    @Override
+    public void dispose()
+        throws IOException
+    {
+        // nothing
+    }
+
+    /** @return size */
+    @Override
+    public int getSize()
+    {
+        return map.size();
+    }
+
+    /** @return stats */
+    @Override
+    public IStats getStatistics()
+    {
+        return null;
+    }
+
+    /**
+     * @return map.keySet().toArray( */
+    @Override
+    public Set<K> getKeySet()
+    {
+        return new LinkedHashSet<>(map.keySet());
+    }
+
+    /**
+     * @param key
+     * @return map.remove( key ) != null
+     * @throws IOException
+     */
+    @Override
+    public boolean remove( K key )
+        throws IOException
+    {
+        return map.remove( key ) != null;
+    }
+
+    /**
+     * @throws IOException
+     */
+    @Override
+    public void removeAll()
+        throws IOException
+    {
+        map.clear();
+    }
+
+    /**
+     * @param key
+     * @return (ICacheElement) map.get( key )
+     * @throws IOException
+     */
+    @Override
+    public ICacheElement<K, V> get( K key )
+        throws IOException
+    {
+        return map.get( key );
+    }
+
+    /**
+     * @param keys
+     * @return elements
+     * @throws IOException
+     */
+    @Override
+    public Map<K, ICacheElement<K, V>> getMultiple(Set<K> keys)
+        throws IOException
+    {
+        Map<K, ICacheElement<K, V>> elements = new HashMap<>();
+
+        if ( keys != null && !keys.isEmpty() )
+        {
+            Iterator<K> iterator = keys.iterator();
+
+            while ( iterator.hasNext() )
+            {
+                K key = iterator.next();
+
+                ICacheElement<K, V> element = get( key );
+
+                if ( element != null )
+                {
+                    elements.put( key, element );
+                }
+            }
+        }
+
+        return elements;
+    }
+
+    /**
+     * @param key
+     * @return (ICacheElement) map.get( key )
+     * @throws IOException
+     */
+    @Override
+    public ICacheElement<K, V> getQuiet( K key )
+        throws IOException
+    {
+        return map.get( key );
+    }
+
+    /**
+     * @param ce
+     * @throws IOException
+     */
+    @Override
+    public void waterfal( ICacheElement<K, V> ce )
+        throws IOException
+    {
+        waterfallCallCount++;
+    }
+
+    /**
+     * @param ce
+     * @throws IOException
+     */
+    @Override
+    public void update( ICacheElement<K, V> ce )
+        throws IOException
+    {
+        if ( ce != null )
+        {
+            map.put( ce.getKey(), ce );
+        }
+    }
+
+    /**
+     * @return ICompositeCacheAttributes
+     */
+    @Override
+    public ICompositeCacheAttributes getCacheAttributes()
+    {
+        return cacheAttr;
+    }
+
+    /**
+     * @param cattr
+     */
+    @Override
+    public void setCacheAttributes( ICompositeCacheAttributes cattr )
+    {
+        this.cacheAttr = cattr;
+    }
+
+    /** @return null */
+    @Override
+    public CompositeCache<K, V> getCompositeCache()
+    {
+        return null;
+    }
+
+    /**
+     * @param group
+     * @return null
+     */
+    public Set<K> getGroupKeys( String group )
+    {
+        return null;
+    }
+
+    /**
+     * @return null
+     */
+    public Set<String> getGroupNames()
+    {
+        return null;
+    }
+
+    /**
+     * @param numberToFree
+     * @return 0
+     * @throws IOException
+     */
+    @Override
+    public int freeElements( int numberToFree )
+        throws IOException
+    {
+        lastNumberOfFreedElements = numberToFree;
+        return 0;
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/memory/fifo/FIFOMemoryCacheUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/memory/fifo/FIFOMemoryCacheUnitTest.java
new file mode 100644
index 0000000..f6095a0
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/memory/fifo/FIFOMemoryCacheUnitTest.java
@@ -0,0 +1,112 @@
+package org.apache.commons.jcs3.engine.memory.fifo;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.CompositeCacheAttributes;
+import org.apache.commons.jcs3.engine.ElementAttributes;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+import org.apache.commons.jcs3.engine.memory.fifo.FIFOMemoryCache;
+
+import junit.framework.TestCase;
+
+/** Unit tests for the fifo implementation. */
+public class FIFOMemoryCacheUnitTest
+    extends TestCase
+{
+    /**
+     * Verify that the oldest inserted item is removed
+     * <p>
+     * @throws IOException
+     */
+    public void testExpirationPolicy_oneExtra()
+        throws IOException
+    {
+        // SETUP
+        int maxObjects = 10;
+        String cacheName = "testExpirationPolicy_oneExtra";
+
+        ICompositeCacheAttributes attributes = new CompositeCacheAttributes();
+        attributes.setCacheName(cacheName);
+        attributes.setMaxObjects( maxObjects );
+        attributes.setSpoolChunkSize( 1 );
+
+        FIFOMemoryCache<String, String> cache = new FIFOMemoryCache<>();
+        cache.initialize( new CompositeCache<>( attributes, new ElementAttributes() ) );
+
+        for ( int i = 0; i <= maxObjects; i++ )
+        {
+            CacheElement<String, String> element = new CacheElement<>( cacheName, "key" + i, "value" + i );
+            cache.update( element );
+        }
+
+        CacheElement<String, String> oneMoreElement = new CacheElement<>( cacheName, "onemore", "onemore" );
+
+        // DO WORK
+        cache.update( oneMoreElement );
+
+        // VERIFY
+        assertEquals( "Should have max elements", maxObjects, cache.getSize() );
+        System.out.println(cache.getKeySet());
+        for ( int i = maxObjects; i > 1; i-- )
+        {
+            assertNotNull( "Should have element " + i, cache.get( "key" + i ) );
+        }
+        assertNotNull( "Should have oneMoreElement", cache.get( "onemore" ) );
+    }
+
+    /**
+     * Verify that the oldest inserted item is removed
+     * <p>
+     * @throws IOException
+     */
+    public void testExpirationPolicy_doubleOver()
+        throws IOException
+    {
+        // SETUP
+        int maxObjects = 10;
+        String cacheName = "testExpirationPolicy_oneExtra";
+
+        ICompositeCacheAttributes attributes = new CompositeCacheAttributes();
+        attributes.setCacheName(cacheName);
+        attributes.setMaxObjects( maxObjects );
+        attributes.setSpoolChunkSize( 1 );
+
+        FIFOMemoryCache<String, String> cache = new FIFOMemoryCache<>();
+        cache.initialize( new CompositeCache<>( attributes, new ElementAttributes() ) );
+
+        // DO WORK
+        for ( int i = 0; i <= (maxObjects * 2); i++ )
+        {
+            CacheElement<String, String> element = new CacheElement<>( cacheName, "key" + i, "value" + i );
+            cache.update( element );
+        }
+
+        // VERIFY
+        assertEquals( "Should have max elements", maxObjects, cache.getSize() );
+        for ( int i = (maxObjects * 2); i > maxObjects; i-- )
+        {
+            assertNotNull( "Shjould have elemnt " + i, cache.get( "key" + i ) );
+        }
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/memory/lru/LHMLRUMemoryCacheConcurrentUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/memory/lru/LHMLRUMemoryCacheConcurrentUnitTest.java
new file mode 100644
index 0000000..b403244
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/memory/lru/LHMLRUMemoryCacheConcurrentUnitTest.java
@@ -0,0 +1,177 @@
+package org.apache.commons.jcs3.engine.memory.lru;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.extensions.ActiveTestSuite;
+import junit.framework.Test;
+import junit.framework.TestCase;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+import org.apache.commons.jcs3.engine.control.CompositeCacheManager;
+import org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache;
+
+/**
+ * Test which exercises the LRUMemory cache. This one uses three different
+ * regions for three threads.
+ */
+public class LHMLRUMemoryCacheConcurrentUnitTest
+    extends TestCase
+{
+    /**
+     * Number of items to cache, twice the configured maxObjects for the memory
+     * cache regions.
+     */
+    private static int items = 200;
+
+    /**
+     * Constructor for the TestDiskCache object.
+     *
+     * @param testName
+     */
+    public LHMLRUMemoryCacheConcurrentUnitTest( String testName )
+    {
+        super( testName );
+    }
+
+    /**
+     * Main method passes this test to the text test runner.
+     *
+     * @param args
+     */
+    public static void main( String args[] )
+    {
+        String[] testCaseName = { LHMLRUMemoryCacheConcurrentUnitTest.class.getName() };
+        junit.textui.TestRunner.main( testCaseName );
+    }
+
+    /**
+     * A unit test suite for JUnit
+     * <p>
+     * @return The test suite
+     */
+    public static Test suite()
+    {
+        ActiveTestSuite suite = new ActiveTestSuite();
+
+        suite.addTest( new LHMLRUMemoryCacheConcurrentUnitTest( "testLHMLRUMemoryCache" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "indexedRegion1" );
+            }
+        } );
+
+        return suite;
+    }
+
+    /**
+     * Test setup
+     */
+    @Override
+    public void setUp()
+    {
+        //JCS.setConfigFilename( "/TestLHMLRUCache.ccf" );
+    }
+
+    /**
+     * Adds items to cache, gets them, and removes them. The item count is more
+     * than the size of the memory cache, so items should be dumped.
+     * <p>
+     * @param region
+     *            Name of the region to access
+     *
+     * @throws Exception
+     *                If an error occurs
+     */
+    public void runTestForRegion( String region )
+        throws Exception
+    {
+        CompositeCacheManager cacheMgr = CompositeCacheManager.getUnconfiguredInstance();
+        cacheMgr.configure( "/TestLHMLRUCache.ccf" );
+        CompositeCache<String, String> cache = cacheMgr.getCache( region );
+
+        LRUMemoryCache<String, String> lru = new LRUMemoryCache<>();
+        lru.initialize( cache );
+
+        // Add items to cache
+
+        for ( int i = 0; i < items; i++ )
+        {
+            ICacheElement<String, String> ice = new CacheElement<>( cache.getCacheName(), i + ":key", region + " data " + i );
+            ice.setElementAttributes( cache.getElementAttributes() );
+            lru.update( ice );
+        }
+
+        // Test that initial items have been purged
+        for ( int i = 0; i < 100; i++ )
+        {
+            assertNull( "Should not have " + i + ":key", lru.get( i + ":key" ) );
+        }
+
+        // Test that last items are in cache
+        for ( int i = 100; i < items; i++ )
+        {
+            String value = lru.get( i + ":key" ).getVal();
+            assertEquals( region + " data " + i, value );
+        }
+
+        // Test that getMultiple returns all the items remaining in cache and none of the missing ones
+        Set<String> keys = new HashSet<>();
+        for ( int i = 0; i < items; i++ )
+        {
+            keys.add( i + ":key" );
+        }
+
+        Map<String, ICacheElement<String, String>> elements = lru.getMultiple( keys );
+        for ( int i = 0; i < 100; i++ )
+        {
+            assertNull( "Should not have " + i + ":key", elements.get( i + ":key" ) );
+        }
+        for ( int i = 100; i < items; i++ )
+        {
+            ICacheElement<String, String> element = elements.get( i + ":key" );
+            assertNotNull( "element " + i + ":key is missing", element );
+            assertEquals( "value " + i + ":key", region + " data " + i, element.getVal() );
+        }
+
+        // Remove all the items
+
+        for ( int i = 0; i < items; i++ )
+        {
+            lru.remove( i + ":key" );
+        }
+
+        // Verify removal
+
+        for ( int i = 0; i < items; i++ )
+        {
+            assertNull( "Removed key should be null: " + i + ":key", lru.get( i + ":key" ) );
+        }
+    }
+
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/memory/lru/LHMLRUMemoryCacheUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/memory/lru/LHMLRUMemoryCacheUnitTest.java
new file mode 100644
index 0000000..c73d915
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/memory/lru/LHMLRUMemoryCacheUnitTest.java
@@ -0,0 +1,314 @@
+package org.apache.commons.jcs3.engine.memory.lru;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.access.exception.CacheException;
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+import org.apache.commons.jcs3.engine.control.CompositeCacheManager;
+import org.apache.commons.jcs3.engine.memory.lru.LHMLRUMemoryCache;
+
+/**
+ * Tests for the test LHMLRU implementation.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class LHMLRUMemoryCacheUnitTest
+    extends TestCase
+{
+    /** Test setup */
+    @Override
+    public void setUp()
+    {
+        JCS.setConfigFilename( "/TestLHMLRUCache.ccf" );
+    }
+
+    /**
+     * Verify that the mru gets used by a non-defined region when it is set as the default in the
+     * default region.
+     * <p>
+     * @throws CacheException
+     */
+    public void testLoadFromCCF()
+        throws CacheException
+    {
+        CacheAccess<String, String> cache = JCS.getInstance( "testLoadFromCCF" );
+        String memoryCacheName = cache.getCacheAttributes().getMemoryCacheName();
+        assertTrue( "Cache name should have LHMLRU in it.", memoryCacheName.indexOf( "LHMLRUMemoryCache" ) != -1 );
+    }
+
+    /**
+     * put twice as many as the max.  verify that the second half is in the cache.
+     * <p>
+     * @throws CacheException
+     */
+    public void testPutGetThroughHub()
+        throws CacheException
+    {
+        CacheAccess<String, String> cache = JCS.getInstance( "testPutGetThroughHub" );
+
+        int max = cache.getCacheAttributes().getMaxObjects();
+        int items = max * 2;
+
+        for ( int i = 0; i < items; i++ )
+        {
+            cache.put( i + ":key", "myregion" + " data " + i );
+        }
+
+        // Test that first items are not in the cache
+        for ( int i = max -1; i >= 0; i-- )
+        {
+            String value = cache.get( i + ":key" );
+            assertNull( "Should not have value for key [" + i + ":key" + "] in the cache." + cache.getStats(), value );
+        }
+
+        // Test that last items are in cache
+        // skip 2 for the buffer.
+        for ( int i = max + 2; i < items; i++ )
+        {
+            String value = cache.get( i + ":key" );
+            assertEquals( "myregion" + " data " + i, value );
+        }
+
+        // Test that getMultiple returns all the items remaining in cache and none of the missing ones
+        Set<String> keys = new HashSet<>();
+        for ( int i = 0; i < items; i++ )
+        {
+            keys.add( i + ":key" );
+        }
+
+        Map<String, ICacheElement<String, String>> elements = cache.getCacheElements( keys );
+        for ( int i = max-1; i >= 0; i-- )
+        {
+            assertNull( "Should not have value for key [" + i + ":key" + "] in the cache." + cache.getStats(), elements.get( i + ":key" ) );
+        }
+        for ( int i = max + 2; i < items; i++ )
+        {
+            ICacheElement<String, String> element = elements.get( i + ":key" );
+            assertNotNull( "element " + i + ":key is missing", element );
+            assertEquals( "value " + i + ":key", "myregion" + " data " + i, element.getVal() );
+        }
+    }
+
+    /**
+     * Put twice as many as the max, twice. verify that the second half is in the cache.
+     * <p>
+     * @throws CacheException
+     */
+    public void testPutGetThroughHubTwice()
+        throws CacheException
+    {
+        CacheAccess<String, String> cache = JCS.getInstance( "testPutGetThroughHubTwice" );
+
+        int max = cache.getCacheAttributes().getMaxObjects();
+        int items = max * 2;
+
+        for ( int i = 0; i < items; i++ )
+        {
+            cache.put( i + ":key", "myregion" + " data " + i );
+        }
+
+        for ( int i = 0; i < items; i++ )
+        {
+            cache.put( i + ":key", "myregion" + " data " + i );
+        }
+
+        // Test that first items are not in the cache
+        for ( int i = max -1; i >= 0; i-- )
+        {
+            String value = cache.get( i + ":key" );
+            assertNull( "Should not have value for key [" + i + ":key" + "] in the cache.", value );
+        }
+
+        // Test that last items are in cache
+        // skip 2 for the buffer.
+        for ( int i = max + 2; i < items; i++ )
+        {
+            String value = cache.get( i + ":key" );
+            assertEquals( "myregion" + " data " + i, value );
+        }
+
+    }
+
+    /**
+     * put the max and remove each. verify that they are all null.
+     * <p>
+     * @throws CacheException
+     */
+    public void testPutRemoveThroughHub()
+        throws CacheException
+    {
+        CacheAccess<String, String> cache = JCS.getInstance( "testPutRemoveThroughHub" );
+
+        int max = cache.getCacheAttributes().getMaxObjects();
+        int items = max * 2;
+
+        for ( int i = 0; i < items; i++ )
+        {
+            cache.put( i + ":key", "myregion" + " data " + i );
+        }
+
+        for ( int i = 0; i < items; i++ )
+        {
+            cache.remove( i + ":key" );
+        }
+
+        // Test that first items are not in the cache
+        for ( int i = max; i >= 0; i-- )
+        {
+            String value = cache.get( i + ":key" );
+            assertNull( "Should not have value for key [" + i + ":key" + "] in the cache.", value );
+        }
+    }
+
+    /**
+     * put the max and clear. verify that no elements remain.
+     * <p>
+     * @throws CacheException
+     */
+    public void testClearThroughHub()
+        throws CacheException
+    {
+        CacheAccess<String, String> cache = JCS.getInstance( "testClearThroughHub" );
+
+        int max = cache.getCacheAttributes().getMaxObjects();
+        int items = max * 2;
+
+        for ( int i = 0; i < items; i++ )
+        {
+            cache.put( i + ":key", "myregion" + " data " + i );
+        }
+
+        cache.clear();
+
+        // Test that first items are not in the cache
+        for ( int i = max; i >= 0; i-- )
+        {
+            String value = cache.get( i + ":key" );
+            assertNull( "Should not have value for key [" + i + ":key" + "] in the cache.", value );
+        }
+    }
+
+    /**
+     * Get stats.
+     * <p>
+     * @throws CacheException
+     */
+    public void testGetStatsThroughHub()
+        throws CacheException
+    {
+        CacheAccess<String, String> cache = JCS.getInstance( "testGetStatsThroughHub" );
+
+        int max = cache.getCacheAttributes().getMaxObjects();
+        int items = max * 2;
+
+        for ( int i = 0; i < items; i++ )
+        {
+            cache.put( i + ":key", "myregion" + " data " + i );
+        }
+
+        String stats = cache.getStats();
+
+        //System.out.println( stats );
+
+        // TODO improve stats check
+        assertTrue( "Should have 200 puts" + stats, stats.indexOf( "200" ) != -1 );
+    }
+
+    /**
+     * Put half the max and clear. get the key array and verify that it has the correct number of
+     * items.
+     * <p>
+     * @throws Exception
+     */
+    public void testGetKeyArray()
+        throws Exception
+    {
+        CompositeCacheManager cacheMgr = CompositeCacheManager.getUnconfiguredInstance();
+        cacheMgr.configure( "/TestLHMLRUCache.ccf" );
+        CompositeCache<String, String> cache = cacheMgr.getCache( "testGetKeyArray" );
+
+        LHMLRUMemoryCache<String, String> mru = new LHMLRUMemoryCache<>();
+        mru.initialize( cache );
+
+        int max = cache.getCacheAttributes().getMaxObjects();
+        int items = max / 2;
+
+        for ( int i = 0; i < items; i++ )
+        {
+            ICacheElement<String, String> ice = new CacheElement<>( cache.getCacheName(), i + ":key", cache.getCacheName() + " data " + i );
+            ice.setElementAttributes( cache.getElementAttributes() );
+            mru.update( ice );
+        }
+
+        Set<String> keys = mru.getKeySet();
+
+        assertEquals( "Wrong number of keys.", items, keys.size() );
+    }
+
+    /**
+     * Add a few keys with the delimiter. Remove them.
+     * <p>
+     * @throws CacheException
+     */
+    public void testRemovePartialThroughHub()
+        throws CacheException
+    {
+        CacheAccess<String, String> cache = JCS.getInstance( "testRemovePartialThroughHub" );
+
+        int max = cache.getCacheAttributes().getMaxObjects();
+        int items = max / 2;
+
+        cache.put( "test", "data" );
+
+        String root = "myroot";
+
+        for ( int i = 0; i < items; i++ )
+        {
+            cache.put( root + ":" + i + ":key", "myregion" + " data " + i );
+        }
+
+        // Test that last items are in cache
+        for ( int i = 0; i < items; i++ )
+        {
+            String value = cache.get( root + ":" + i + ":key" );
+            assertEquals( "myregion" + " data " + i, value );
+        }
+
+        // remove partial
+        cache.remove( root + ":" );
+
+        for ( int i = 0; i < items; i++ )
+        {
+            assertNull( "Should have been removed by partial loop.", cache.get( root + ":" + i + ":key" ) );
+        }
+
+        assertNotNull( "Other item should be in the cache.", cache.get( "test" ) );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/memory/lru/LRUMemoryCacheConcurrentUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/memory/lru/LRUMemoryCacheConcurrentUnitTest.java
new file mode 100644
index 0000000..4af77dc
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/memory/lru/LRUMemoryCacheConcurrentUnitTest.java
@@ -0,0 +1,173 @@
+package org.apache.commons.jcs3.engine.memory.lru;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.extensions.ActiveTestSuite;
+import junit.framework.Test;
+import junit.framework.TestCase;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+import org.apache.commons.jcs3.engine.control.CompositeCacheManager;
+import org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache;
+
+/**
+ * Test which exercises the LRUMemory cache. This one uses three different
+ * regions for three threads.
+ */
+public class LRUMemoryCacheConcurrentUnitTest
+    extends TestCase
+{
+    /**
+     * Number of items to cache, twice the configured maxObjects for the memory
+     * cache regions.
+     */
+    private static int items = 200;
+
+    /**
+     * Constructor for the TestDiskCache object.
+     * <p>
+     * @param testName
+     */
+    public LRUMemoryCacheConcurrentUnitTest( String testName )
+    {
+        super( testName );
+    }
+
+    /**
+     * Main method passes this test to the text test runner.
+     * <p>
+     * @param args
+     */
+    public static void main( String args[] )
+    {
+        String[] testCaseName = { LRUMemoryCacheConcurrentUnitTest.class.getName() };
+        junit.textui.TestRunner.main( testCaseName );
+    }
+
+    /**
+     * A unit test suite for JUnit
+     * <p>
+     * @return The test suite
+     */
+    public static Test suite()
+    {
+        ActiveTestSuite suite = new ActiveTestSuite();
+
+        suite.addTest( new LRUMemoryCacheConcurrentUnitTest( "testLRUMemoryCache" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runTestForRegion( "testRegion1" );
+            }
+        } );
+
+        return suite;
+    }
+
+    /**
+     * Test setup
+     */
+    @Override
+    public void setUp()
+    {
+        //JCS.setConfigFilename( "/TestDiskCache.ccf" );
+    }
+
+    /**
+     * Adds items to cache, gets them, and removes them. The item count is more
+     * than the size of the memory cache, so items should be dumped.
+     * <p>
+     * @param region
+     *            Name of the region to access
+     * @throws Exception
+     *                If an error occurs
+     */
+    public void runTestForRegion( String region )
+        throws Exception
+    {
+        CompositeCacheManager cacheMgr = CompositeCacheManager.getUnconfiguredInstance();
+        cacheMgr.configure( "/TestDiskCache.ccf" );
+        CompositeCache<String, String> cache = cacheMgr.getCache( region );
+
+        LRUMemoryCache<String, String> lru = new LRUMemoryCache<>();
+        lru.initialize( cache );
+
+        // Add items to cache
+
+        for ( int i = 0; i < items; i++ )
+        {
+            ICacheElement<String, String> ice = new CacheElement<>( cache.getCacheName(), i + ":key", region + " data " + i );
+            ice.setElementAttributes( cache.getElementAttributes() );
+            lru.update( ice );
+        }
+
+        // Test that initial items have been purged
+        for ( int i = 0; i < 100; i++ )
+        {
+            assertNull( "Should not have " + i + ":key", lru.get( i + ":key" ) );
+        }
+
+        // Test that last items are in cache
+        for ( int i = 100; i < items; i++ )
+        {
+            String value = lru.get( i + ":key" ).getVal();
+            assertEquals( region + " data " + i, value );
+        }
+
+        // Test that getMultiple returns all the items remaining in cache and none of the missing ones
+        Set<String> keys = new HashSet<>();
+        for ( int i = 0; i < items; i++ )
+        {
+            keys.add( i + ":key" );
+        }
+
+        Map<String, ICacheElement<String, String>> elements = lru.getMultiple( keys );
+        for ( int i = 0; i < 100; i++ )
+        {
+            assertNull( "Should not have " + i + ":key", elements.get( i + ":key" ) );
+        }
+        for ( int i = 100; i < items; i++ )
+        {
+            ICacheElement<String, String> element = elements.get( i + ":key" );
+            assertNotNull( "element " + i + ":key is missing", element );
+            assertEquals( "value " + i + ":key", region + " data " + i, element.getVal() );
+        }
+
+        // Remove all the items
+        for ( int i = 0; i < items; i++ )
+        {
+            lru.remove( i + ":key" );
+        }
+
+        // Verify removal
+        for ( int i = 0; i < items; i++ )
+        {
+            assertNull( "Removed key should be null: " + i + ":key", lru.get( i + ":key" ) );
+        }
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/memory/mru/LRUvsMRUPerformanceTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/memory/mru/LRUvsMRUPerformanceTest.java
new file mode 100644
index 0000000..b2524c7
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/memory/mru/LRUvsMRUPerformanceTest.java
@@ -0,0 +1,185 @@
+package org.apache.commons.jcs3.engine.memory.mru;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache;
+import org.apache.commons.jcs3.engine.memory.mru.MRUMemoryCache;
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the performance difference between the LRU and the MRU. There should be very little.
+ */
+public class LRUvsMRUPerformanceTest
+    extends TestCase
+{
+    /** ration we want */
+    float ratioPut = 0;
+
+    /** ration we want */
+    float ratioGet = 0;
+
+    /** ration we want */
+    float target = 1.20f;
+
+    /** times to run */
+    int loops = 20;
+
+    /** item per run */
+    int tries = 10000;
+
+    /**
+     * A unit test for JUnit
+     * @throws Exception Description of the Exception
+     */
+    public void testSimpleLoad()
+        throws Exception
+    {
+        Log log1 = LogManager.getLog( LRUMemoryCache.class );
+        if ( log1.isDebugEnabled() )
+        {
+            System.out.println( "The log level must be at info or above for the a performance test." );
+            return;
+        }
+        Log log2 = LogManager.getLog( MRUMemoryCache.class );
+        if ( log2.isDebugEnabled() )
+        {
+            System.out.println( "The log level must be at info or above for the a performance test." );
+            return;
+        }
+        doWork();
+
+        // these were when the mru was implemented with the jdk linked list
+        //assertTrue( "Ratio is unacceptible.", this.ratioPut < target );
+        ///assertTrue( "Ratio is unacceptible.", this.ratioGet < target );
+    }
+
+    /**
+     * Runs the test
+     */
+    public void doWork()
+    {
+
+        long start = 0;
+        long end = 0;
+        long time = 0;
+        float tPer = 0;
+
+        long putTotalLRU = 0;
+        long getTotalLRU = 0;
+        long putTotalMRU = 0;
+        long getTotalMRU = 0;
+
+        try
+        {
+
+            JCS.setConfigFilename( "/TestMRUCache.ccf" );
+            CacheAccess<String, String> cache = JCS.getInstance( "lruDefined" );
+            CacheAccess<String, String> mru = JCS.getInstance( "mruDefined" );
+
+            System.out.println( "LRU = " + cache );
+
+            for ( int j = 0; j < loops; j++ )
+            {
+
+                System.out.println( "Beginning loop " + j );
+
+                String name = "LRU      ";
+                start = System.currentTimeMillis();
+                for ( int i = 0; i < tries; i++ )
+                {
+                    cache.put( "key:" + i, "data" + i );
+                }
+                end = System.currentTimeMillis();
+                time = end - start;
+                putTotalLRU += time;
+                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
+                System.out.println( name + " put time for " + tries + " = " + time + "; millis per = " + tPer );
+
+                start = System.currentTimeMillis();
+                for ( int i = 0; i < tries; i++ )
+                {
+                    cache.get( "key:" + i );
+                }
+                end = System.currentTimeMillis();
+                time = end - start;
+                getTotalLRU += time;
+                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
+                System.out.println( name + " get time for " + tries + " = " + time + "; millis per = " + tPer );
+
+                // /////////////////////////////////////////////////////////////
+                name = "MRU";
+                start = System.currentTimeMillis();
+                for ( int i = 0; i < tries; i++ )
+                {
+                    mru.put( "key:" + i, "data" + i );
+                }
+                end = System.currentTimeMillis();
+                time = end - start;
+                putTotalMRU += time;
+                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
+                System.out.println( name + " put time for " + tries + " = " + time + "; millis per = " + tPer );
+
+                start = System.currentTimeMillis();
+                for ( int i = 0; i < tries; i++ )
+                {
+                    mru.get( "key:" + i );
+                }
+                end = System.currentTimeMillis();
+                time = end - start;
+                getTotalMRU += time;
+                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
+                System.out.println( name + " get time for " + tries + " = " + time + "; millis per = " + tPer );
+
+                System.out.println( "\n" );
+            }
+
+        }
+        catch ( Exception e )
+        {
+            e.printStackTrace( System.out );
+            System.out.println( e );
+        }
+
+        long putAvJCS = putTotalLRU / loops;
+        long getAvJCS = getTotalLRU / loops;
+        long putAvHashtable = putTotalMRU / loops;
+        long getAvHashtable = getTotalMRU / loops;
+
+        System.out.println( "Finished " + loops + " loops of " + tries + " gets and puts" );
+
+        System.out.println( "\n" );
+        System.out.println( "Put average for JCS       = " + putAvJCS );
+        System.out.println( "Put average for MRU = " + putAvHashtable );
+        ratioPut = Float.intBitsToFloat( (int) putAvJCS ) / Float.intBitsToFloat( (int) putAvHashtable );
+        System.out.println( "JCS puts took " + ratioPut + " times the Hashtable, the goal is <" + target + "x" );
+
+        System.out.println( "\n" );
+        System.out.println( "Get average for JCS       = " + getAvJCS );
+        System.out.println( "Get average for MRU = " + getAvHashtable );
+        ratioGet = Float.intBitsToFloat( (int) getAvJCS ) / Float.intBitsToFloat( (int) getAvHashtable );
+        System.out.println( "JCS gets took " + ratioGet + " times the Hashtable, the goal is <" + target + "x" );
+    }
+
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/memory/mru/MRUMemoryCacheUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/memory/mru/MRUMemoryCacheUnitTest.java
new file mode 100644
index 0000000..42ce655
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/memory/mru/MRUMemoryCacheUnitTest.java
@@ -0,0 +1,314 @@
+package org.apache.commons.jcs3.engine.memory.mru;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.access.exception.CacheException;
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+import org.apache.commons.jcs3.engine.control.CompositeCacheManager;
+import org.apache.commons.jcs3.engine.memory.mru.MRUMemoryCache;
+
+/**
+ * Tests for the test MRU implementation.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class MRUMemoryCacheUnitTest
+    extends TestCase
+{
+    /** Test setup */
+    @Override
+    public void setUp()
+    {
+        JCS.setConfigFilename( "/TestMRUCache.ccf" );
+    }
+
+    /**
+     * Verify that the mru gets used by a non-defined region when it is set as the default in the
+     * default region.
+     * <p>
+     * @throws CacheException
+     */
+    public void testLoadFromCCF()
+        throws CacheException
+    {
+        CacheAccess<String, String> cache = JCS.getInstance( "testPutGet" );
+        String memoryCacheName = cache.getCacheAttributes().getMemoryCacheName();
+        assertTrue( "Cache name should have MRU in it.", memoryCacheName.indexOf( "MRUMemoryCache" ) != -1 );
+    }
+
+    /**
+     * put twice as many as the max.  verify that the second half is in the cache.
+     * <p>
+     * @throws CacheException
+     */
+    public void testPutGetThroughHub()
+        throws CacheException
+    {
+        CacheAccess<String, String> cache = JCS.getInstance( "testPutGetThroughHub" );
+
+        int max = cache.getCacheAttributes().getMaxObjects();
+        int items = max * 2;
+
+        for ( int i = 0; i < items; i++ )
+        {
+            cache.put( i + ":key", "myregion" + " data " + i );
+        }
+
+        // Test that first items are not in the cache
+        for ( int i = max -1; i >= 0; i-- )
+        {
+            String value = cache.get( i + ":key" );
+            assertNull( "Should not have value for key [" + i + ":key" + "] in the cache." + cache.getStats(), value );
+        }
+
+        // Test that last items are in cache
+        // skip 2 for the buffer.
+        for ( int i = max + 2; i < items; i++ )
+        {
+            String value = cache.get( i + ":key" );
+            assertEquals( "myregion" + " data " + i, value );
+        }
+
+        // Test that getMultiple returns all the items remaining in cache and none of the missing ones
+        Set<String> keys = new HashSet<>();
+        for ( int i = 0; i < items; i++ )
+        {
+            keys.add( i + ":key" );
+        }
+
+        Map<String, ICacheElement<String, String>> elements = cache.getCacheElements( keys );
+        for ( int i = max-1; i >= 0; i-- )
+        {
+            assertNull( "Should not have value for key [" + i + ":key" + "] in the cache." + cache.getStats(), elements.get( i + ":key" ) );
+        }
+        for ( int i = max + 2; i < items; i++ )
+        {
+            ICacheElement<String, String> element = elements.get( i + ":key" );
+            assertNotNull( "element " + i + ":key is missing", element );
+            assertEquals( "value " + i + ":key", "myregion" + " data " + i, element.getVal() );
+        }
+    }
+
+    /**
+     * Put twice as many as the max, twice. verify that the second half is in the cache.
+     * <p>
+     * @throws CacheException
+     */
+    public void testPutGetThroughHubTwice()
+        throws CacheException
+    {
+        CacheAccess<String, String> cache = JCS.getInstance( "testPutGetThroughHub" );
+
+        int max = cache.getCacheAttributes().getMaxObjects();
+        int items = max * 2;
+
+        for ( int i = 0; i < items; i++ )
+        {
+            cache.put( i + ":key", "myregion" + " data " + i );
+        }
+
+        for ( int i = 0; i < items; i++ )
+        {
+            cache.put( i + ":key", "myregion" + " data " + i );
+        }
+
+        // Test that first items are not in the cache
+        for ( int i = max-1; i >= 0; i-- )
+        {
+            String value = cache.get( i + ":key" );
+            assertNull( "Should not have value for key [" + i + ":key" + "] in the cache.", value );
+        }
+
+        // Test that last items are in cache
+        // skip 2 for the buffer.
+        for ( int i = max + 2; i < items; i++ )
+        {
+            String value = cache.get( i + ":key" );
+            assertEquals( "myregion" + " data " + i, value );
+        }
+
+    }
+
+    /**
+     * put the max and remove each. verify that they are all null.
+     * <p>
+     * @throws CacheException
+     */
+    public void testPutRemoveThroughHub()
+        throws CacheException
+    {
+        CacheAccess<String, String> cache = JCS.getInstance( "testPutGetThroughHub" );
+
+        int max = cache.getCacheAttributes().getMaxObjects();
+        int items = max * 2;
+
+        for ( int i = 0; i < items; i++ )
+        {
+            cache.put( i + ":key", "myregion" + " data " + i );
+        }
+
+        for ( int i = 0; i < items; i++ )
+        {
+            cache.remove( i + ":key" );
+        }
+
+        // Test that first items are not in the cache
+        for ( int i = max; i >= 0; i-- )
+        {
+            String value = cache.get( i + ":key" );
+            assertNull( "Should not have value for key [" + i + ":key" + "] in the cache.", value );
+        }
+    }
+
+    /**
+     * put the max and clear. verify that no elements remain.
+     * <p>
+     * @throws CacheException
+     */
+    public void testClearThroughHub()
+        throws CacheException
+    {
+        CacheAccess<String, String> cache = JCS.getInstance( "testPutGetThroughHub" );
+
+        int max = cache.getCacheAttributes().getMaxObjects();
+        int items = max * 2;
+
+        for ( int i = 0; i < items; i++ )
+        {
+            cache.put( i + ":key", "myregion" + " data " + i );
+        }
+
+        cache.clear();
+
+        // Test that first items are not in the cache
+        for ( int i = max; i >= 0; i-- )
+        {
+            String value = cache.get( i + ":key" );
+            assertNull( "Should not have value for key [" + i + ":key" + "] in the cache.", value );
+        }
+    }
+
+    /**
+     * Get stats.
+     * <p>
+     * @throws CacheException
+     */
+    public void testGetStatsThroughHub()
+        throws CacheException
+    {
+        CacheAccess<String, String> cache = JCS.getInstance( "testGetStatsThroughHub" );
+
+        int max = cache.getCacheAttributes().getMaxObjects();
+        int items = max * 2;
+
+        for ( int i = 0; i < items; i++ )
+        {
+            cache.put( i + ":key", "myregion" + " data " + i );
+        }
+
+        String stats = cache.getStats();
+
+//        System.out.println( stats );
+
+        // TODO improve stats check
+        assertTrue( "Should have 200 puts", stats.indexOf( "2000" ) != -1 );
+    }
+
+    /**
+     * Put half the max and clear. get the key array and verify that it has the correct number of
+     * items.
+     * <p>
+     * @throws Exception
+     */
+    public void testGetKeyArray()
+        throws Exception
+    {
+        CompositeCacheManager cacheMgr = CompositeCacheManager.getUnconfiguredInstance();
+        cacheMgr.configure( "/TestMRUCache.ccf" );
+        CompositeCache<String, String> cache = cacheMgr.getCache( "testGetKeyArray" );
+
+        MRUMemoryCache<String, String> mru = new MRUMemoryCache<>();
+        mru.initialize( cache );
+
+        int max = cache.getCacheAttributes().getMaxObjects();
+        int items = max / 2;
+
+        for ( int i = 0; i < items; i++ )
+        {
+            ICacheElement<String, String> ice = new CacheElement<>( cache.getCacheName(), i + ":key", cache.getCacheName() + " data " + i );
+            ice.setElementAttributes( cache.getElementAttributes() );
+            mru.update( ice );
+        }
+
+        Set<String> keys = mru.getKeySet();
+
+        assertEquals( "Wrong number of keys.", items, keys.size() );
+    }
+
+    /**
+     * Add a few keys with the delimiter. Remove them.
+     * <p>
+     * @throws CacheException
+     */
+    public void testRemovePartialThroughHub()
+        throws CacheException
+    {
+        CacheAccess<String, String> cache = JCS.getInstance( "testGetStatsThroughHub" );
+
+        int max = cache.getCacheAttributes().getMaxObjects();
+        int items = max / 2;
+
+        cache.put( "test", "data" );
+
+        String root = "myroot";
+
+        for ( int i = 0; i < items; i++ )
+        {
+            cache.put( root + ":" + i + ":key", "myregion" + " data " + i );
+        }
+
+        // Test that last items are in cache
+        for ( int i = 0; i < items; i++ )
+        {
+            String value = cache.get( root + ":" + i + ":key" );
+            assertEquals( "myregion" + " data " + i, value );
+        }
+
+        // remove partial
+        cache.remove( root + ":" );
+
+        for ( int i = 0; i < items; i++ )
+        {
+            assertNull( "Should have been removed by partial loop.", cache.get( root + ":" + i + ":key" ) );
+        }
+
+        assertNotNull( "Other item should be in the cache.", cache.get( "test" ) );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/memory/shrinking/ShrinkerThreadUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/memory/shrinking/ShrinkerThreadUnitTest.java
new file mode 100644
index 0000000..99ee54c
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/memory/shrinking/ShrinkerThreadUnitTest.java
@@ -0,0 +1,338 @@
+package org.apache.commons.jcs3.engine.memory.shrinking;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+import org.apache.commons.jcs3.engine.ElementAttributesUtils;
+import org.apache.commons.jcs3.engine.control.event.ElementEventHandlerMockImpl;
+import org.apache.commons.jcs3.engine.memory.MockMemoryCache;
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.CompositeCacheAttributes;
+import org.apache.commons.jcs3.engine.ElementAttributes;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+import org.apache.commons.jcs3.engine.control.event.behavior.ElementEventType;
+import org.apache.commons.jcs3.engine.memory.shrinking.ShrinkerThread;
+
+import java.io.IOException;
+
+/**
+ * This tests the functionality of the shrinker thread.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class ShrinkerThreadUnitTest
+    extends TestCase
+{
+    /** verify the check for removal
+     * <p>
+     * @throws IOException */
+    public void testCheckForRemoval_Expired() throws IOException
+    {
+        // SETUP
+        CompositeCacheAttributes cacheAttr = new CompositeCacheAttributes();
+        cacheAttr.setCacheName("testRegion");
+        cacheAttr.setMaxMemoryIdleTimeSeconds( 10 );
+        cacheAttr.setMaxSpoolPerRun( 10 );
+
+        CompositeCache<String, String> cache = new CompositeCache<>(cacheAttr, new ElementAttributes());
+
+        String key = "key";
+        String value = "value";
+
+        ICacheElement<String, String> element = new CacheElement<>( "testRegion", key, value );
+        ElementAttributes elementAttr = new ElementAttributes();
+        elementAttr.setIsEternal( false );
+        element.setElementAttributes( elementAttr );
+        element.getElementAttributes().setMaxLife(1);
+
+        long now = System.currentTimeMillis();
+        // add two seconds
+        now += 2000;
+
+        // DO WORK
+        boolean result = cache.isExpired( element, now,
+                ElementEventType.EXCEEDED_MAXLIFE_BACKGROUND,
+                ElementEventType.EXCEEDED_IDLETIME_BACKGROUND );
+
+        // VERIFY
+        assertTrue( "Item should have expired.", result );
+    }
+
+    /** verify the check for removal
+     * <p>
+     * @throws IOException */
+    public void testCheckForRemoval_NotExpired() throws IOException
+    {
+        // SETUP
+        CompositeCacheAttributes cacheAttr = new CompositeCacheAttributes();
+        cacheAttr.setCacheName("testRegion");
+        cacheAttr.setMaxMemoryIdleTimeSeconds( 10 );
+        cacheAttr.setMaxSpoolPerRun( 10 );
+
+        CompositeCache<String, String> cache = new CompositeCache<>(cacheAttr, new ElementAttributes());
+
+        String key = "key";
+        String value = "value";
+
+        ICacheElement<String, String> element = new CacheElement<>( "testRegion", key, value );
+        ElementAttributes elementAttr = new ElementAttributes();
+        elementAttr.setIsEternal( false );
+        element.setElementAttributes( elementAttr );
+        element.getElementAttributes().setMaxLife(1);
+
+        long now = System.currentTimeMillis();
+        // subtract two seconds
+        now -= 2000;
+
+        // DO WORK
+        boolean result = cache.isExpired( element, now,
+                ElementEventType.EXCEEDED_MAXLIFE_BACKGROUND,
+                ElementEventType.EXCEEDED_IDLETIME_BACKGROUND );
+
+        // VERIFY
+        assertFalse( "Item should not have expired.", result );
+    }
+
+    /** verify the check for removal
+     * <p>
+     * @throws IOException */
+    public void testCheckForRemoval_IdleTooLong() throws IOException
+    {
+        // SETUP
+        CompositeCacheAttributes cacheAttr = new CompositeCacheAttributes();
+        cacheAttr.setCacheName("testRegion");
+        cacheAttr.setMaxMemoryIdleTimeSeconds( 10 );
+        cacheAttr.setMaxSpoolPerRun( 10 );
+
+        CompositeCache<String, String> cache = new CompositeCache<>(cacheAttr, new ElementAttributes());
+
+        String key = "key";
+        String value = "value";
+
+        ICacheElement<String, String> element = new CacheElement<>( "testRegion", key, value );
+        ElementAttributes elementAttr = new ElementAttributes();
+        elementAttr.setIsEternal( false );
+        element.setElementAttributes( elementAttr );
+        element.getElementAttributes().setMaxLife(100);
+        element.getElementAttributes().setIdleTime( 1 );
+
+        long now = System.currentTimeMillis();
+        // add two seconds
+        now += 2000;
+
+        // DO WORK
+        boolean result = cache.isExpired( element, now,
+                ElementEventType.EXCEEDED_MAXLIFE_BACKGROUND,
+                ElementEventType.EXCEEDED_IDLETIME_BACKGROUND );
+
+        // VERIFY
+        assertTrue( "Item should have expired.", result );
+    }
+
+    /** verify the check for removal
+     * <p>
+     * @throws IOException */
+    public void testCheckForRemoval_NotIdleTooLong() throws IOException
+    {
+        // SETUP
+        CompositeCacheAttributes cacheAttr = new CompositeCacheAttributes();
+        cacheAttr.setCacheName("testRegion");
+        cacheAttr.setMaxMemoryIdleTimeSeconds( 10 );
+        cacheAttr.setMaxSpoolPerRun( 10 );
+
+        CompositeCache<String, String> cache = new CompositeCache<>(cacheAttr, new ElementAttributes());
+
+        String key = "key";
+        String value = "value";
+
+        ICacheElement<String, String> element = new CacheElement<>( "testRegion", key, value );
+        ElementAttributes elementAttr = new ElementAttributes();
+        elementAttr.setIsEternal( false );
+        element.setElementAttributes( elementAttr );
+        element.getElementAttributes().setMaxLife(100);
+        element.getElementAttributes().setIdleTime( 1 );
+
+        long now = System.currentTimeMillis();
+        // subtract two seconds
+        now -= 2000;
+
+        // DO WORK
+        boolean result = cache.isExpired( element, now,
+                ElementEventType.EXCEEDED_MAXLIFE_BACKGROUND,
+                ElementEventType.EXCEEDED_IDLETIME_BACKGROUND );
+
+        // VERIFY
+        assertFalse( "Item should not have expired.", result );
+    }
+
+    /**
+     * Setup cache attributes in mock. Create the shrinker with the mock. Add some elements into the
+     * mock memory cache see that they get spooled.
+     * <p>
+     * @throws Exception
+     */
+    public void testSimpleShrink()
+        throws Exception
+    {
+        // SETUP
+        CompositeCacheAttributes cacheAttr = new CompositeCacheAttributes();
+        cacheAttr.setCacheName("testRegion");
+        cacheAttr.setMemoryCacheName("org.apache.commons.jcs3.engine.memory.MockMemoryCache");
+        cacheAttr.setMaxMemoryIdleTimeSeconds( 1 );
+        cacheAttr.setMaxSpoolPerRun( 10 );
+
+        CompositeCache<String, String> cache = new CompositeCache<>(cacheAttr, new ElementAttributes());
+        MockMemoryCache<String, String> memory = (MockMemoryCache<String, String>)cache.getMemoryCache();
+
+        String key = "key";
+        String value = "value";
+
+        ICacheElement<String, String> element = new CacheElement<>( "testRegion", key, value );
+
+        ElementAttributes elementAttr = new ElementAttributes();
+        elementAttr.setIsEternal( false );
+        element.setElementAttributes( elementAttr );
+        element.getElementAttributes().setMaxLife(1);
+        memory.update( element );
+
+        ICacheElement<String, String> returnedElement1 = memory.get( key );
+        assertNotNull( "We should have received an element", returnedElement1 );
+
+        // set this to 2 seconds ago.
+        ElementAttributesUtils.setLastAccessTime( elementAttr,  System.currentTimeMillis() - 2000 );
+
+        // DO WORK
+        ShrinkerThread<String, String> shrinker = new ShrinkerThread<>( cache );
+        shrinker.run();
+
+        Thread.sleep( 500 );
+
+        // VERIFY
+        ICacheElement<String, String> returnedElement2 = memory.get( key );
+        assertTrue( "Waterfall should have been called.", memory.waterfallCallCount > 0 );
+        assertNull( "We not should have received an element.  It should have been spooled.", returnedElement2 );
+    }
+
+    /**
+     * Add 10 to the memory cache. Set the spool per run limit to 3.
+     * <p>
+     * @throws Exception
+     */
+    public void testSimpleShrinkMultiple()
+        throws Exception
+    {
+        // SETUP
+        CompositeCacheAttributes cacheAttr = new CompositeCacheAttributes();
+        cacheAttr.setCacheName("testRegion");
+        cacheAttr.setMemoryCacheName("org.apache.commons.jcs3.engine.memory.MockMemoryCache");
+        cacheAttr.setMaxMemoryIdleTimeSeconds( 1 );
+        cacheAttr.setMaxSpoolPerRun( 3 );
+
+        CompositeCache<String, String> cache = new CompositeCache<>(cacheAttr, new ElementAttributes());
+        MockMemoryCache<String, String> memory = (MockMemoryCache<String, String>)cache.getMemoryCache();
+
+        for ( int i = 0; i < 10; i++ )
+        {
+            String key = "key" + i;
+            String value = "value";
+
+            ICacheElement<String, String> element = new CacheElement<>( "testRegion", key, value );
+
+            ElementAttributes elementAttr = new ElementAttributes();
+            elementAttr.setIsEternal( false );
+            element.setElementAttributes( elementAttr );
+            element.getElementAttributes().setMaxLife(1);
+            memory.update( element );
+
+            ICacheElement<String, String> returnedElement1 = memory.get( key );
+            assertNotNull( "We should have received an element", returnedElement1 );
+
+            // set this to 2 seconds ago.
+            ElementAttributesUtils.setLastAccessTime( elementAttr,  System.currentTimeMillis() - 2000 );
+        }
+
+        // DO WORK
+        ShrinkerThread<String, String> shrinker = new ShrinkerThread<>( cache );
+        shrinker.run();
+
+        // VERIFY
+        Thread.sleep( 500 );
+        assertEquals( "Waterfall called the wrong number of times.", 3, memory.waterfallCallCount );
+        assertEquals( "Wrong number of elements remain.", 7, memory.getSize() );
+    }
+
+    /**
+     * Add a mock event handler to the items. Verify that it gets called.
+     * <p>
+     * This is only testing the spooled background event
+     * <p>
+     * @throws Exception
+     */
+    public void testSimpleShrinkMultipleWithEventHandler()
+        throws Exception
+    {
+        // SETUP
+        CompositeCacheAttributes cacheAttr = new CompositeCacheAttributes();
+        cacheAttr.setCacheName("testRegion");
+        cacheAttr.setMemoryCacheName("org.apache.commons.jcs3.engine.memory.MockMemoryCache");
+        cacheAttr.setMaxMemoryIdleTimeSeconds( 1 );
+        cacheAttr.setMaxSpoolPerRun( 3 );
+
+        CompositeCache<String, String> cache = new CompositeCache<>(cacheAttr, new ElementAttributes());
+        MockMemoryCache<String, String> memory = (MockMemoryCache<String, String>)cache.getMemoryCache();
+
+        ElementEventHandlerMockImpl handler = new ElementEventHandlerMockImpl();
+
+        for ( int i = 0; i < 10; i++ )
+        {
+            String key = "key" + i;
+            String value = "value";
+
+            ICacheElement<String, String> element = new CacheElement<>( "testRegion", key, value );
+
+            ElementAttributes elementAttr = new ElementAttributes();
+            elementAttr.addElementEventHandler( handler );
+            elementAttr.setIsEternal( false );
+            element.setElementAttributes( elementAttr );
+            element.getElementAttributes().setMaxLife(1);
+            memory.update( element );
+
+            ICacheElement<String, String> returnedElement1 = memory.get( key );
+            assertNotNull( "We should have received an element", returnedElement1 );
+
+            // set this to 2 seconds ago.
+            ElementAttributesUtils.setLastAccessTime( elementAttr,  System.currentTimeMillis() - 2000 );
+        }
+
+        // DO WORK
+        ShrinkerThread<String, String> shrinker = new ShrinkerThread<>( cache );
+        shrinker.run();
+
+        // VERIFY
+        Thread.sleep( 500 );
+        assertEquals( "Waterfall called the wrong number of times.", 3, memory.waterfallCallCount );
+        // the shrinker delegates the the composite cache on the memory cache to put the
+        // event on the queue.  This make it hard to test.  TODO we need to change this to make it easier to verify.
+        //assertEquals( "Event handler ExceededIdleTimeBackground called the wrong number of times.", 3, handler.getExceededIdleTimeBackgroundCount() );
+        assertEquals( "Wrong number of elements remain.", 7, memory.getSize() );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/memory/soft/SoftReferenceMemoryCacheUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/memory/soft/SoftReferenceMemoryCacheUnitTest.java
new file mode 100644
index 0000000..b2aa8c1
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/memory/soft/SoftReferenceMemoryCacheUnitTest.java
@@ -0,0 +1,248 @@
+package org.apache.commons.jcs3.engine.memory.soft;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jcs3.JCS;
+import org.apache.commons.jcs3.access.CacheAccess;
+import org.apache.commons.jcs3.access.exception.CacheException;
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+import org.apache.commons.jcs3.engine.control.CompositeCacheManager;
+import org.apache.commons.jcs3.engine.memory.soft.SoftReferenceMemoryCache;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for the test Soft reference implementation.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class SoftReferenceMemoryCacheUnitTest
+    extends TestCase
+{
+    /** Test setup */
+    @Override
+    public void setUp()
+    {
+        JCS.setConfigFilename( "/TestSoftReferenceCache.ccf" );
+    }
+
+    /**
+     * @see junit.framework.TestCase#tearDown()
+     */
+    @Override
+    protected void tearDown() throws Exception
+    {
+        CompositeCacheManager.getInstance().shutDown();
+    }
+
+    /**
+     * Verify that the cache gets used by a non-defined region when it is set as the default in the
+     * default region.
+     * <p>
+     * @throws CacheException
+     */
+    public void testLoadFromCCF()
+        throws CacheException
+    {
+        CacheAccess<String, String> cache = JCS.getInstance( "testPutGet" );
+        String memoryCacheName = cache.getCacheAttributes().getMemoryCacheName();
+        assertTrue( "Cache name should have SoftReference in it.",
+                memoryCacheName.indexOf( "SoftReferenceMemoryCache" ) != -1 );
+    }
+
+    /**
+     * put twice as many as the max.  verify that all are in the cache.
+     * <p>
+     * @throws CacheException
+     */
+    public void testPutGetThroughHub()
+        throws CacheException
+    {
+        CacheAccess<String, String> cache = JCS.getInstance( "testPutGetThroughHub" );
+
+        int max = cache.getCacheAttributes().getMaxObjects();
+        int items = max * 2;
+
+        for ( int i = 0; i < items; i++ )
+        {
+            cache.put( i + ":key", "myregion" + " data " + i );
+        }
+
+        // Test that all items are in cache
+        for ( int i = 0; i < items; i++ )
+        {
+            String value = cache.get( i + ":key" );
+            assertEquals( "myregion" + " data " + i, value );
+        }
+
+        // Test that getMultiple returns all the items remaining in cache and none of the missing ones
+        Set<String> keys = new HashSet<>();
+        for ( int i = 0; i < items; i++ )
+        {
+            keys.add( i + ":key" );
+        }
+
+        Map<String, ICacheElement<String, String>> elements = cache.getCacheElements( keys );
+        for ( int i = 0; i < items; i++ )
+        {
+            ICacheElement<String, String> element = elements.get( i + ":key" );
+            assertNotNull( "element " + i + ":key is missing", element );
+            assertEquals( "value " + i + ":key", "myregion" + " data " + i, element.getVal() );
+        }
+
+        // System.out.println(cache.getStats());
+    }
+
+    /**
+     * put the max and remove each. verify that they are all null.
+     * <p>
+     * @throws CacheException
+     */
+    public void testPutRemoveThroughHub()
+        throws CacheException
+    {
+        CacheAccess<String, String> cache = JCS.getInstance( "testPutGetThroughHub" );
+
+        int max = cache.getCacheAttributes().getMaxObjects();
+        int items = max * 2;
+
+        for ( int i = 0; i < items; i++ )
+        {
+            cache.put( i + ":key", "myregion" + " data " + i );
+        }
+
+        for ( int i = 0; i < items; i++ )
+        {
+            cache.remove( i + ":key" );
+        }
+
+        // Test that first items are not in the cache
+        for ( int i = max; i >= 0; i-- )
+        {
+            String value = cache.get( i + ":key" );
+            assertNull( "Should not have value for key [" + i + ":key" + "] in the cache.", value );
+        }
+    }
+
+    /**
+     * put the max and clear. verify that no elements remain.
+     * <p>
+     * @throws CacheException
+     */
+    public void testClearThroughHub()
+        throws CacheException
+    {
+        CacheAccess<String, String> cache = JCS.getInstance( "testPutGetThroughHub" );
+
+        int max = cache.getCacheAttributes().getMaxObjects();
+        int items = max * 2;
+
+        for ( int i = 0; i < items; i++ )
+        {
+            cache.put( i + ":key", "myregion" + " data " + i );
+        }
+
+        cache.clear();
+
+        // Test that first items are not in the cache
+        for ( int i = max; i >= 0; i-- )
+        {
+            String value = cache.get( i + ":key" );
+            assertNull( "Should not have value for key [" + i + ":key" + "] in the cache.", value );
+        }
+    }
+
+    /**
+     * Put half the max and clear. get the key array and verify that it has the correct number of
+     * items.
+     * <p>
+     * @throws Exception
+     */
+    public void testGetKeyArray()
+        throws Exception
+    {
+        CompositeCacheManager cacheMgr = CompositeCacheManager.getUnconfiguredInstance();
+        cacheMgr.configure( "/TestSoftReferenceCache.ccf" );
+        CompositeCache<String, String> cache = cacheMgr.getCache( "testGetKeyArray" );
+
+        SoftReferenceMemoryCache<String, String> srmc = new SoftReferenceMemoryCache<>();
+        srmc.initialize( cache );
+
+        int max = cache.getCacheAttributes().getMaxObjects();
+        int items = max / 2;
+
+        for ( int i = 0; i < items; i++ )
+        {
+            ICacheElement<String, String> ice = new CacheElement<>( cache.getCacheName(), i + ":key", cache.getCacheName() + " data " + i );
+            ice.setElementAttributes( cache.getElementAttributes() );
+            srmc.update( ice );
+        }
+
+        Set<String> keys = srmc.getKeySet();
+
+        assertEquals( "Wrong number of keys.", items, keys.size() );
+    }
+
+    /**
+     * Add a few keys with the delimiter. Remove them.
+     * <p>
+     * @throws CacheException
+     */
+    public void testRemovePartialThroughHub()
+        throws CacheException
+    {
+        CacheAccess<String, String> cache = JCS.getInstance( "testGetStatsThroughHub" );
+
+        int max = cache.getCacheAttributes().getMaxObjects();
+        int items = max / 2;
+
+        cache.put( "test", "data" );
+
+        String root = "myroot";
+
+        for ( int i = 0; i < items; i++ )
+        {
+            cache.put( root + ":" + i + ":key", "myregion" + " data " + i );
+        }
+
+        // Test that last items are in cache
+        for ( int i = 0; i < items; i++ )
+        {
+            String value = cache.get( root + ":" + i + ":key" );
+            assertEquals( "myregion" + " data " + i, value );
+        }
+
+        // remove partial
+        cache.remove( root + ":" );
+
+        for ( int i = 0; i < items; i++ )
+        {
+            assertNull( "Should have been removed by partial loop.", cache.get( root + ":" + i + ":key" ) );
+        }
+
+        assertNotNull( "Other item should be in the cache.", cache.get( "test" ) );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/access/JCSWorkerUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/access/JCSWorkerUnitTest.java
new file mode 100644
index 0000000..bb78ce8
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/access/JCSWorkerUnitTest.java
@@ -0,0 +1,73 @@
+package org.apache.commons.jcs3.utils.access;
+
+import org.apache.commons.jcs3.utils.access.AbstractJCSWorkerHelper;
+import org.apache.commons.jcs3.utils.access.JCSWorker;
+import org.apache.commons.jcs3.utils.access.JCSWorkerHelper;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/**
+ * Test cases for the JCS worker.
+ *
+ * @author Aaron Smuts
+ *
+ */
+public class JCSWorkerUnitTest
+    extends TestCase
+{
+
+    /**
+     * Test basic worker functionality.  This is a serial not a concurrent test.
+     * <p>
+     * Just verify that the worker will go to the cache before asking the helper.
+     *
+     * @throws Exception
+     *
+     */
+    public void testSimpleGet()
+        throws Exception
+    {
+        JCSWorker<String, Long> cachingWorker = new JCSWorker<>( "example region" );
+
+        // This is the helper.
+        JCSWorkerHelper<Long> helper = new AbstractJCSWorkerHelper<Long>()
+        {
+            int timesCalled = 0;
+
+            @Override
+            public Long doWork()
+            {
+                return Long.valueOf( ++timesCalled );
+            }
+        };
+
+        String key = "abc";
+
+        Long result = cachingWorker.getResult( key, helper );
+        assertEquals( "Called the wrong number of times", Long.valueOf( 1 ), result );
+
+        // should get it from the cache.
+        Long result2 = cachingWorker.getResult( key, helper );
+        assertEquals( "Called the wrong number of times", Long.valueOf( 1 ), result2 );
+    }
+
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/config/PropertySetterUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/config/PropertySetterUnitTest.java
new file mode 100644
index 0000000..36e3fbb
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/config/PropertySetterUnitTest.java
@@ -0,0 +1,62 @@
+package org.apache.commons.jcs3.utils.config;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import static org.junit.Assert.*;
+
+import java.io.File;
+
+import org.apache.commons.jcs3.utils.config.PropertySetter;
+import org.junit.Test;
+
+/**
+ * Test property settings
+ *
+ * @author Thomas Vandahl
+ *
+ */
+public class PropertySetterUnitTest
+{
+    enum EnumTest { ONE, TWO, THREE };
+
+    @Test
+    public void testConvertArg()
+    {
+        PropertySetter ps = new PropertySetter(this);
+        Object s = ps.convertArg("test", String.class);
+        assertEquals("Should be a string", "test", s);
+
+        Object i = ps.convertArg("1", Integer.TYPE);
+        assertEquals("Should be an integer", Integer.valueOf(1), i);
+
+        Object l = ps.convertArg("1", Long.TYPE);
+        assertEquals("Should be a long", Long.valueOf(1), l);
+
+        Object b = ps.convertArg("true", Boolean.TYPE);
+        assertEquals("Should be a boolean", Boolean.TRUE, b);
+
+        Object e = ps.convertArg("TWO", EnumTest.class);
+        assertEquals("Should be an enum", EnumTest.TWO, e);
+
+        Object f = ps.convertArg("test.conf", File.class);
+        assertTrue("Should be a file", f instanceof File);
+    }
+
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/discovery/MockDiscoveryListener.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/discovery/MockDiscoveryListener.java
new file mode 100644
index 0000000..a16fd1d
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/discovery/MockDiscoveryListener.java
@@ -0,0 +1,57 @@
+package org.apache.commons.jcs3.utils.discovery;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.jcs3.utils.discovery.DiscoveredService;
+import org.apache.commons.jcs3.utils.discovery.behavior.IDiscoveryListener;
+
+/** Mock listener, for testing. */
+public class MockDiscoveryListener
+    implements IDiscoveryListener
+{
+    /** discovered services. */
+    public List<DiscoveredService> discoveredServices = new ArrayList<>();
+
+    /**
+     * Adds the entry to a list. I'm not using a set. I want to see if we get dupes.
+     * <p>
+     * @param service
+     */
+    @Override
+    public void addDiscoveredService( DiscoveredService service )
+    {
+        discoveredServices.add( service );
+    }
+
+    /**
+     * Removes it from the list.
+     * <p>
+     * @param service
+     */
+    @Override
+    public void removeDiscoveredService( DiscoveredService service )
+    {
+        discoveredServices.remove( service );
+    }
+
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoverySenderUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoverySenderUnitTest.java
new file mode 100644
index 0000000..20ed743
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoverySenderUnitTest.java
@@ -0,0 +1,158 @@
+package org.apache.commons.jcs3.utils.discovery;
+
+import java.util.ArrayList;
+
+import org.apache.commons.jcs3.utils.discovery.UDPDiscoveryMessage;
+import org.apache.commons.jcs3.utils.discovery.UDPDiscoveryReceiver;
+import org.apache.commons.jcs3.utils.discovery.UDPDiscoverySender;
+import org.apache.commons.jcs3.utils.discovery.UDPDiscoveryMessage.BroadcastType;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for the sender.
+ */
+public class UDPDiscoverySenderUnitTest
+    extends TestCase
+{
+    /** multicast address to send/receive on */
+    private static final String ADDRESS = "228.4.5.9";
+
+    /** multicast address to send/receive on */
+    private static final int PORT = 5556;
+
+    /** imaginary host address for sending */
+    private static final String SENDING_HOST = "imaginary host address";
+
+    /** imaginary port for sending */
+    private static final int SENDING_PORT = 1;
+
+    /** receiver instance for tests */
+    private UDPDiscoveryReceiver receiver;
+
+    /** sender instance for tests */
+    private UDPDiscoverySender sender;
+
+    /**
+     * Set up the receiver. Maybe better to just code sockets here? Set up the sender for sending
+     * the message.
+     * <p>
+     * @throws Exception on error
+     */
+    @Override
+    protected void setUp()
+        throws Exception
+    {
+        super.setUp();
+        receiver = new UDPDiscoveryReceiver( null, null, ADDRESS, PORT );
+        sender = new UDPDiscoverySender( ADDRESS, PORT, 0 );
+    }
+
+    /**
+     * Kill off the sender and receiver.
+     * <p>
+     * @throws Exception on error
+     */
+    @Override
+    protected void tearDown()
+        throws Exception
+    {
+        receiver.shutdown();
+        sender.close();
+        super.tearDown();
+    }
+
+    /**
+     * Test sending a live messages.
+     * <p>
+     * @throws Exception on error
+     */
+    public void testPassiveBroadcast()
+        throws Exception
+    {
+        // SETUP
+        ArrayList<String> cacheNames = new ArrayList<>();
+
+        // DO WORK
+        sender.passiveBroadcast( SENDING_HOST, SENDING_PORT, cacheNames, 1L );
+
+        // VERIFY
+        // grab the sent message
+        Object obj = receiver.waitForMessage() ;
+
+        assertTrue( "unexpected crap received", obj instanceof UDPDiscoveryMessage );
+
+        UDPDiscoveryMessage msg = (UDPDiscoveryMessage) obj;
+        // disabled test because of JCS-89
+        // assertEquals( "wrong host", SENDING_HOST, msg.getHost() );
+        assertEquals( "wrong port", SENDING_PORT, msg.getPort() );
+        assertEquals( "wrong message type", BroadcastType.PASSIVE, msg.getMessageType() );
+    }
+
+    /**
+     * Test sending a remove broadcast.
+     * <p>
+     * @throws Exception on error
+     */
+    public void testRemoveBroadcast()
+        throws Exception
+    {
+        // SETUP
+        ArrayList<String> cacheNames = new ArrayList<>();
+
+        // DO WORK
+        sender.removeBroadcast( SENDING_HOST, SENDING_PORT, cacheNames, 1L );
+
+        // VERIFY
+        // grab the sent message
+        Object obj = receiver.waitForMessage();
+
+        assertTrue( "unexpected crap received", obj instanceof UDPDiscoveryMessage );
+
+        UDPDiscoveryMessage msg = (UDPDiscoveryMessage) obj;
+        // disabled test because of JCS-89
+        // assertEquals( "wrong host", SENDING_HOST, msg.getHost() );
+        assertEquals( "wrong port", SENDING_PORT, msg.getPort() );
+        assertEquals( "wrong message type", BroadcastType.REMOVE, msg.getMessageType() );
+    }
+
+    /**
+     * Test sending a request broadcast.
+     * <p>
+     * @throws Exception on error
+     */
+    public void testRequestBroadcast()
+        throws Exception
+    {
+        // DO WORK
+        sender.requestBroadcast();
+
+        // VERIFY
+        // grab the sent message
+        Object obj = receiver.waitForMessage();
+
+        assertTrue( "unexpected crap received", obj instanceof UDPDiscoveryMessage );
+
+        UDPDiscoveryMessage msg = (UDPDiscoveryMessage) obj;
+        assertEquals( "wrong message type", BroadcastType.REQUEST, msg.getMessageType() );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoveryServiceUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoveryServiceUnitTest.java
new file mode 100644
index 0000000..6fc4a7d
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoveryServiceUnitTest.java
@@ -0,0 +1,230 @@
+package org.apache.commons.jcs3.utils.discovery;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.ArrayList;
+
+import org.apache.commons.jcs3.utils.discovery.DiscoveredService;
+import org.apache.commons.jcs3.utils.discovery.UDPDiscoveryAttributes;
+import org.apache.commons.jcs3.utils.discovery.UDPDiscoveryService;
+
+import junit.framework.TestCase;
+
+/** Unit tests for the service. */
+public class UDPDiscoveryServiceUnitTest
+    extends TestCase
+{
+    /** Verify that the list is updated. */
+    public void testAddOrUpdateService_NotInList()
+    {
+        // SETUP
+        String host = "228.5.6.7";
+        int port = 6789;
+        UDPDiscoveryAttributes attributes = new UDPDiscoveryAttributes();
+        attributes.setUdpDiscoveryAddr( host );
+        attributes.setUdpDiscoveryPort( port );
+        attributes.setServicePort( 1000 );
+
+        // create the service
+        UDPDiscoveryService service = new UDPDiscoveryService( attributes );
+        service.startup();
+        service.addParticipatingCacheName( "testCache1" );
+
+        MockDiscoveryListener discoveryListener = new MockDiscoveryListener();
+        service.addDiscoveryListener( discoveryListener );
+
+        DiscoveredService discoveredService = new DiscoveredService();
+        discoveredService.setServiceAddress( host );
+        discoveredService.setCacheNames( new ArrayList<>() );
+        discoveredService.setServicePort( 1000 );
+        discoveredService.setLastHearFromTime( 100 );
+
+        // DO WORK
+        service.addOrUpdateService( discoveredService );
+
+        // VERIFY
+        assertTrue( "Service should be in the service list.", service.getDiscoveredServices()
+            .contains( discoveredService ) );
+        assertTrue( "Service should be in the listener list.", discoveryListener.discoveredServices
+            .contains( discoveredService ) );
+    }
+
+    /** Verify that the list is updated. */
+    public void testAddOrUpdateService_InList_NamesDoNotChange()
+    {
+        // SETUP
+        String host = "228.5.6.7";
+        int port = 6789;
+        UDPDiscoveryAttributes attributes = new UDPDiscoveryAttributes();
+        attributes.setUdpDiscoveryAddr( host );
+        attributes.setUdpDiscoveryPort( port );
+        attributes.setServicePort( 1000 );
+
+        // create the service
+        UDPDiscoveryService service = new UDPDiscoveryService( attributes );
+        service.startup();
+        service.addParticipatingCacheName( "testCache1" );
+
+        MockDiscoveryListener discoveryListener = new MockDiscoveryListener();
+        service.addDiscoveryListener( discoveryListener );
+
+        ArrayList<String> sametCacheNames = new ArrayList<>();
+        sametCacheNames.add( "name1" );
+
+        DiscoveredService discoveredService = new DiscoveredService();
+        discoveredService.setServiceAddress( host );
+        discoveredService.setCacheNames( sametCacheNames );
+        discoveredService.setServicePort( 1000 );
+        discoveredService.setLastHearFromTime( 100 );
+
+
+        DiscoveredService discoveredService2 = new DiscoveredService();
+        discoveredService2.setServiceAddress( host );
+        discoveredService2.setCacheNames( sametCacheNames );
+        discoveredService2.setServicePort( 1000 );
+        discoveredService2.setLastHearFromTime( 500 );
+
+        // DO WORK
+        service.addOrUpdateService( discoveredService );
+        // again
+        service.addOrUpdateService( discoveredService2 );
+
+        // VERIFY
+        assertEquals( "Should only be one in the set.", 1, service.getDiscoveredServices().size() );
+        assertTrue( "Service should be in the service list.", service.getDiscoveredServices()
+            .contains( discoveredService ) );
+        assertTrue( "Service should be in the listener list.", discoveryListener.discoveredServices
+            .contains( discoveredService ) );
+
+        // need to update the time this sucks. add has no effect convert to a map
+        for (DiscoveredService service1 : service.getDiscoveredServices())
+        {
+            if ( discoveredService.equals( service1 ) )
+            {
+                assertEquals( "The match should have the new last heard from time.", service1.getLastHearFromTime(),
+                              discoveredService2.getLastHearFromTime() );
+            }
+        }
+        // the mock has a list from all add calls.
+        // it should have been called when the list changed.
+        //assertEquals( "Mock should have been called once.", 1, discoveryListener.discoveredServices.size() );
+        // logic changed.  it's called every time.
+        assertEquals( "Mock should have been called twice.", 2, discoveryListener.discoveredServices.size() );
+    }
+
+    /** Verify that the list is updated. */
+    public void testAddOrUpdateService_InList_NamesChange()
+    {
+        // SETUP
+        String host = "228.5.6.7";
+        int port = 6789;
+        UDPDiscoveryAttributes attributes = new UDPDiscoveryAttributes();
+        attributes.setUdpDiscoveryAddr( host );
+        attributes.setUdpDiscoveryPort( port );
+        attributes.setServicePort( 1000 );
+
+        // create the service
+        UDPDiscoveryService service = new UDPDiscoveryService( attributes );
+        service.startup();
+        service.addParticipatingCacheName( "testCache1" );
+
+        MockDiscoveryListener discoveryListener = new MockDiscoveryListener();
+        service.addDiscoveryListener( discoveryListener );
+
+        DiscoveredService discoveredService = new DiscoveredService();
+        discoveredService.setServiceAddress( host );
+        discoveredService.setCacheNames( new ArrayList<>() );
+        discoveredService.setServicePort( 1000 );
+        discoveredService.setLastHearFromTime( 100 );
+
+        ArrayList<String> differentCacheNames = new ArrayList<>();
+        differentCacheNames.add( "name1" );
+        DiscoveredService discoveredService2 = new DiscoveredService();
+        discoveredService2.setServiceAddress( host );
+        discoveredService2.setCacheNames( differentCacheNames );
+        discoveredService2.setServicePort( 1000 );
+        discoveredService2.setLastHearFromTime( 500 );
+
+        // DO WORK
+        service.addOrUpdateService( discoveredService );
+        // again
+        service.addOrUpdateService( discoveredService2 );
+
+        // VERIFY
+        assertEquals( "Should only be one in the set.", 1, service.getDiscoveredServices().size() );
+        assertTrue( "Service should be in the service list.", service.getDiscoveredServices()
+            .contains( discoveredService ) );
+        assertTrue( "Service should be in the listener list.", discoveryListener.discoveredServices
+            .contains( discoveredService ) );
+
+        // need to update the time this sucks. add has no effect convert to a map
+        for (DiscoveredService service1 : service.getDiscoveredServices())
+        {
+            if ( discoveredService.equals( service1 ) )
+            {
+                assertEquals( "The match should have the new last heard from time.", service1.getLastHearFromTime(),
+                              discoveredService2.getLastHearFromTime() );
+                assertEquals( "The names should be updated.", service1.getCacheNames() + "", differentCacheNames + "" );
+            }
+        }
+        // the mock has a list from all add calls.
+        // it should have been called when the list changed.
+        assertEquals( "Mock should have been called twice.", 2, discoveryListener.discoveredServices.size() );
+        assertEquals( "The second mock listener add should be discoveredService2", discoveredService2,
+                      discoveryListener.discoveredServices.get( 1 ) );
+    }
+
+    /** Verify that the list is updated. */
+    public void testRemoveDiscoveredService()
+    {
+        // SETUP
+        String host = "228.5.6.7";
+        int port = 6789;
+        UDPDiscoveryAttributes attributes = new UDPDiscoveryAttributes();
+        attributes.setUdpDiscoveryAddr( host );
+        attributes.setUdpDiscoveryPort( port );
+        attributes.setServicePort( 1000 );
+
+        // create the service
+        UDPDiscoveryService service = new UDPDiscoveryService( attributes );
+        service.startup();
+        service.addParticipatingCacheName( "testCache1" );
+
+        MockDiscoveryListener discoveryListener = new MockDiscoveryListener();
+        service.addDiscoveryListener( discoveryListener );
+
+        DiscoveredService discoveredService = new DiscoveredService();
+        discoveredService.setServiceAddress( host );
+        discoveredService.setCacheNames( new ArrayList<>() );
+        discoveredService.setServicePort( 1000 );
+        discoveredService.setLastHearFromTime( 100 );
+
+        service.addOrUpdateService( discoveredService );
+
+        // DO WORK
+        service.removeDiscoveredService( discoveredService );
+
+        // VERIFY
+        assertFalse( "Service should not be in the service list.", service.getDiscoveredServices()
+            .contains( discoveredService ) );
+        assertFalse( "Service should not be in the listener list.", discoveryListener.discoveredServices
+            .contains( discoveredService ) );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoveryUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoveryUnitTest.java
new file mode 100644
index 0000000..0980193
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoveryUnitTest.java
@@ -0,0 +1,101 @@
+package org.apache.commons.jcs3.utils.discovery;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.ArrayList;
+
+import org.apache.commons.jcs3.utils.timing.SleepUtil;
+import org.apache.commons.jcs3.utils.discovery.UDPDiscoveryAttributes;
+import org.apache.commons.jcs3.utils.discovery.UDPDiscoveryReceiver;
+import org.apache.commons.jcs3.utils.discovery.UDPDiscoverySender;
+import org.apache.commons.jcs3.utils.discovery.UDPDiscoveryService;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for discovery
+ */
+public class UDPDiscoveryUnitTest
+    extends TestCase
+{
+    /**
+     * <p>
+     * @throws Exception
+     */
+    public void testSimpleUDPDiscovery()
+        throws Exception
+    {
+        UDPDiscoveryAttributes attributes = new UDPDiscoveryAttributes();
+        attributes.setUdpDiscoveryAddr( /*"FF7E:230::1234"*/ "228.5.6.7" );
+        attributes.setUdpDiscoveryPort( 6789 );
+        attributes.setServicePort( 1000 );
+
+        // create the service
+        UDPDiscoveryService service = new UDPDiscoveryService( attributes );
+        service.startup();
+        service.addParticipatingCacheName( "testCache1" );
+
+        MockDiscoveryListener discoveryListener = new MockDiscoveryListener();
+        service.addDiscoveryListener( discoveryListener );
+
+        // create a receiver with the service
+        UDPDiscoveryReceiver receiver = new UDPDiscoveryReceiver( service,
+                null,
+                attributes.getUdpDiscoveryAddr(),
+                attributes.getUdpDiscoveryPort() );
+        Thread t = new Thread( receiver );
+        t.start();
+
+        // create a sender
+        UDPDiscoverySender sender = new UDPDiscoverySender(
+                attributes.getUdpDiscoveryAddr(),
+                attributes.getUdpDiscoveryPort(),
+                4 /* datagram TTL */);
+
+        // create more names than we have no wait facades for
+        // the only one that gets added should be testCache1
+        ArrayList<String> cacheNames = new ArrayList<>();
+        int numJunk = 10;
+        for ( int i = 0; i < numJunk; i++ )
+        {
+            cacheNames.add( "junkCacheName" + i );
+        }
+        cacheNames.add( "testCache1" );
+
+        // send max messages
+        int max = 10;
+        int cnt = 0;
+        for ( ; cnt < max; cnt++ )
+        {
+            sender.passiveBroadcast( "localhost", 1111, cacheNames, 1 );
+            SleepUtil.sleepAtLeast( 20 );
+        }
+
+        SleepUtil.sleepAtLeast( 200 );
+
+        // check to see that we got 10 messages
+        //System.out.println( "Receiver count = " + receiver.getCnt() );
+
+        // request braodcasts change things.
+        assertTrue( "Receiver count [" + receiver.getCnt() + "] should be the at least the number sent [" + cnt + "].",
+                    cnt <= receiver.getCnt() );
+        sender.close();
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/net/HostNameUtilUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/net/HostNameUtilUnitTest.java
new file mode 100644
index 0000000..5810cc3
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/net/HostNameUtilUnitTest.java
@@ -0,0 +1,46 @@
+package org.apache.commons.jcs3.utils.net;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+import java.net.UnknownHostException;
+
+import org.apache.commons.jcs3.utils.net.HostNameUtil;
+
+/** Tests for the host name util. */
+public class HostNameUtilUnitTest
+    extends TestCase
+{
+    /**
+     * It's nearly impossible to unit test the getLocalHostLANAddress method.
+     * <p>
+     * @throws UnknownHostException
+     */
+    public void testGetLocalHostAddress_Simple() throws UnknownHostException
+    {
+        // DO WORK
+        String result = HostNameUtil.getLocalHostAddress();
+
+        // VERIFY
+        //System.out.print( result );
+        assertNotNull( "Should have a host address.", result );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/props/PropertyLoader.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/props/PropertyLoader.java
new file mode 100644
index 0000000..e15ce28
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/props/PropertyLoader.java
@@ -0,0 +1,160 @@
+package org.apache.commons.jcs3.utils.props;
+
+import java.io.IOException;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.InputStream;
+import java.util.Properties;
+
+/**
+ * I modified this class to work with .ccf files in particular. I also removed
+ * the resource bundle functionality.
+ * <p>
+ * A simple class for loading java.util.Properties backed by .ccf files deployed
+ * as classpath resources. See individual methods for details.
+ * <p>
+ * The original source is from:
+ * <p>
+ * @author (C) <a
+ *         href="http://www.javaworld.com/columns/jw-qna-index.shtml">Vlad
+ *         Roubtsov </a>, 2003
+ */
+public abstract class PropertyLoader
+{
+    /** throw an error if we can load the file */
+    private static final boolean THROW_ON_LOAD_FAILURE = true;
+
+    /** File suffix. */
+    private static final String SUFFIX = ".ccf";
+
+    /** property suffix */
+    private static final String SUFFIX_PROPERTIES = ".properties";
+
+    /**
+     * Looks up a resource named 'name' in the classpath. The resource must map
+     * to a file with .ccf extention. The name is assumed to be absolute and can
+     * use either "/" or "." for package segment separation with an optional
+     * leading "/" and optional ".ccf" suffix.
+     * <p>
+     * The suffix ".ccf" will be appended if it is not set. This can also handle
+     * .properties files
+     * <p>
+     * Thus, the following names refer to the same resource:
+     *
+     * <pre>
+     *
+     *       some.pkg.Resource
+     *       some.pkg.Resource.ccf
+     *       some/pkg/Resource
+     *       some/pkg/Resource.ccf
+     *       /some/pkg/Resource
+     *       /some/pkg/Resource.ccf
+     * </pre>
+     *
+     * @param name
+     *            classpath resource name [may not be null]
+     * @param loader
+     *            classloader through which to load the resource [null is
+     *            equivalent to the application loader]
+     * @return resource converted to java.util.properties [may be null if the
+     *         resource was not found and THROW_ON_LOAD_FAILURE is false]
+     * @throws IllegalArgumentException
+     *             if the resource was not found and THROW_ON_LOAD_FAILURE is
+     *             true
+     */
+    public static Properties loadProperties( String name, ClassLoader loader )
+    {
+        boolean isCCFSuffix = true;
+
+        if ( name == null )
+        {
+            throw new IllegalArgumentException( "null input: name" );
+        }
+
+        ClassLoader classLoader = ( loader == null ) ? ClassLoader.getSystemClassLoader() : loader;
+
+        String fileName = name.startsWith( "/" ) ? name.substring( 1 ) : name;
+
+        if ( fileName.endsWith( SUFFIX ) )
+        {
+            fileName = fileName.substring( 0, fileName.length() - SUFFIX.length() );
+        }
+
+        if ( fileName.endsWith( SUFFIX_PROPERTIES ) )
+        {
+            fileName = fileName.substring( 0, fileName.length() - SUFFIX_PROPERTIES.length() );
+            isCCFSuffix = false;
+        }
+
+        fileName = fileName.replace( '.', '/' );
+
+        if ( !fileName.endsWith( SUFFIX ) && isCCFSuffix )
+        {
+            fileName = fileName.concat( SUFFIX );
+        }
+        else if ( !fileName.endsWith( SUFFIX_PROPERTIES ) && !isCCFSuffix )
+        {
+            fileName = fileName.concat( SUFFIX_PROPERTIES );
+        }
+
+        Properties result = null;
+
+        try (InputStream in = classLoader.getResourceAsStream( fileName ))
+        {
+            result = new Properties();
+            result.load( in ); // can throw IOException
+        }
+        catch ( IOException e )
+        {
+            result = null;
+        }
+
+        if ( THROW_ON_LOAD_FAILURE && result == null )
+        {
+            throw new IllegalArgumentException( "could not load [" + fileName + "]" + " as " + "a classloader resource" );
+        }
+
+        return result;
+    }
+
+    /**
+     * A convenience overload of {@link #loadProperties(String, ClassLoader)}
+     * that uses the current thread's context classloader. A better strategy
+     * would be to use techniques shown in
+     * http://www.javaworld.com/javaworld/javaqa/2003-06/01-qa-0606-load.html
+     * <p>
+     * @param name
+     * @return Properties
+     */
+    public static Properties loadProperties( final String name )
+    {
+        return loadProperties( name, Thread.currentThread().getContextClassLoader() );
+    }
+
+    /**
+     * Can't use this one.
+     */
+    private PropertyLoader()
+    {
+        super();
+    } // this class is not extentible
+
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/serialization/CompressingSerializerUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/serialization/CompressingSerializerUnitTest.java
new file mode 100644
index 0000000..8ea76ee
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/serialization/CompressingSerializerUnitTest.java
@@ -0,0 +1,122 @@
+package org.apache.commons.jcs3.utils.serialization;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+
+import org.apache.commons.jcs3.utils.serialization.CompressingSerializer;
+import org.apache.commons.jcs3.utils.serialization.StandardSerializer;
+
+/**
+ * Tests the compressing serializer.
+ */
+public class CompressingSerializerUnitTest
+    extends TestCase
+{
+    /**
+     * Verify that we don't get any erorrs for null input.
+     * <p>
+     * @throws ClassNotFoundException
+     * @throws IOException
+     */
+    public void testDeserialize_NullInput()
+        throws IOException, ClassNotFoundException
+    {
+        // SETUP
+        CompressingSerializer serializer = new CompressingSerializer();
+
+        // DO WORK
+        Object result = serializer.deSerialize( null, null );
+
+        // VERIFY
+        assertNull( "Should have nothing.", result );
+    }
+
+    /**
+     * Test simple back and forth with a string.
+     * <p>
+     * ))&lt;=&gt;((
+     * <p>
+     * @throws Exception on error
+     */
+    public void testSimpleBackAndForth()
+        throws Exception
+    {
+        // SETUP
+        CompressingSerializer serializer = new CompressingSerializer();
+
+        // DO WORK
+        String before = "adsfdsafdsafdsafdsafdsafdsafdsagfdsafdsafdsfdsafdsafsa333 31231";
+        String after = (String) serializer.deSerialize( serializer.serialize( before ), null );
+
+        // VERIFY
+        assertEquals( "Before and after should be the same.", before, after );
+    }
+
+    /**
+     * Test serialization with a null object. Verify that we don't get an error.
+     * <p>
+     * @throws Exception on error
+     */
+    public void testSerialize_NullInput()
+        throws Exception
+    {
+        // SETUP
+        CompressingSerializer serializer = new CompressingSerializer();
+
+        String before = null;
+
+        // DO WORK
+        byte[] serialized = serializer.serialize( before );
+        String after = (String) serializer.deSerialize( serialized, null );
+
+        // VERIFY
+        assertNull( "Should have nothing. after =" + after, after );
+    }
+
+    /**
+     * Verify that the compressed is smaller.
+     * <p>
+     * @throws Exception on error
+     */
+    public void testSerialize_CompareCompressedAndUncompressed()
+        throws Exception
+    {
+        // SETUP
+        CompressingSerializer serializer = new CompressingSerializer();
+
+        // I hate for loops.
+        String before = "adsfdsafdsafdsafdsafdsafdsafdsagfdsafdsafdssaf dsaf sadf dsaf dsaf dsaf "
+            + "dsafdsa fdsaf dsaf dsafdsa dsaf dsaf dsaf dsaf dsafdsa76f dsa798f dsa6fdsa 087f  "
+            + "gh 987dsahb dsahbuhbfnui nufdsa hbv87 f8vhdsgbnfv h8fdg8dfjvn8fdwgj fdsgjb9fdsjbv"
+            + "jvhjv hg98f-dsaghj j9fdsb gfsb 9fdshjbgb987fdsbfdwgh ujbhjbhb hbfdsgh fdshb "
+            + "Ofdsgyfesgyfdsafdsafsa333 31231";
+
+        // DO WORK
+        byte[] compressed = serializer.serialize( before );
+        byte[] nonCompressed = new StandardSerializer().serialize( before );
+
+        // VERIFY
+        assertTrue( "Compressed should be smaller. compressed size = " + compressed.length + "nonCompressed size = "
+            + nonCompressed.length, compressed.length < nonCompressed.length );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/serialization/SerializationConversionUtilUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/serialization/SerializationConversionUtilUnitTest.java
new file mode 100644
index 0000000..af7d0c8
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/serialization/SerializationConversionUtilUnitTest.java
@@ -0,0 +1,198 @@
+package org.apache.commons.jcs3.utils.serialization;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.ElementAttributes;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheElementSerialized;
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.utils.serialization.SerializationConversionUtil;
+import org.apache.commons.jcs3.utils.serialization.StandardSerializer;
+
+/**
+ * Tests the serialization conversion util.
+ * <p>
+ * @author Aaron Smuts
+ */
+public class SerializationConversionUtilUnitTest
+    extends TestCase
+{
+    /**
+     * Verify null for null.
+     * <p>
+     * @throws IOException
+     */
+    public void testgGetSerializedCacheElement_null()
+        throws IOException
+    {
+        // SETUP
+        IElementSerializer elementSerializer = new StandardSerializer();
+        ICacheElement<String, String> before = null;
+
+        // DO WORK
+        ICacheElementSerialized<String, String> result =
+            SerializationConversionUtil.getSerializedCacheElement( before, elementSerializer );
+
+        // VERIFY
+        assertNull( "Should get null for null", result );
+    }
+
+    /**
+     * Verify null for null.
+     * <p>
+     * @throws Exception
+     */
+    public void testgGetDeSerializedCacheElement_null()
+        throws Exception
+    {
+        // SETUP
+        IElementSerializer elementSerializer = new StandardSerializer();
+        ICacheElementSerialized<String, String> before = null;
+
+        // DO WORK
+        ICacheElement<String, String> result =
+            SerializationConversionUtil.getDeSerializedCacheElement( before, elementSerializer );
+
+        // VERIFY
+        assertNull( "Should get null for null", result );
+    }
+
+    /**
+     * Verify that we can go back and forth with the simplest of objects.
+     * <p>
+     * @throws Exception
+     */
+    public void testSimpleConversion()
+        throws Exception
+    {
+        // SETUP
+        String cacheName = "testName";
+        String key = "key";
+        String value = "value fdsadf dsafdsa fdsaf dsafdsaf dsafdsaf dsaf dsaf dsaf dsafa dsaf dsaf dsafdsaf";
+
+        IElementSerializer elementSerializer = new StandardSerializer();
+
+        IElementAttributes attr = new ElementAttributes();
+        attr.setMaxLife(34);
+
+        ICacheElement<String, String> before = new CacheElement<>( cacheName, key, value );
+        before.setElementAttributes( attr );
+
+        // DO WORK
+        ICacheElementSerialized<String, String> serialized =
+            SerializationConversionUtil.getSerializedCacheElement( before, elementSerializer );
+
+        // VERIFY
+        assertNotNull( "Should have a serialized object.", serialized );
+
+        // DO WORK
+        ICacheElement<String, String> after =
+            SerializationConversionUtil.getDeSerializedCacheElement( serialized, elementSerializer );
+
+        // VERIFY
+        assertNotNull( "Should have a deserialized object.", after );
+        assertEquals( "Values should be the same.", before.getVal(), after.getVal() );
+        assertEquals( "Attributes should be the same.", before.getElementAttributes().getMaxLife(), after
+            .getElementAttributes().getMaxLife() );
+        assertEquals( "Keys should be the same.", before.getKey(), after.getKey() );
+        assertEquals( "Cache name should be the same.", before.getCacheName(), after.getCacheName() );
+    }
+
+    /**
+     * Verify that we can go back and forth with the simplest of objects.
+     *<p>
+     * @throws Exception
+     */
+    public void testAccidentalDoubleConversion()
+        throws Exception
+    {
+        // SETUP
+        String cacheName = "testName";
+        String key = "key";
+        String value = "value fdsadf dsafdsa fdsaf dsafdsaf dsafdsaf dsaf dsaf dsaf dsafa dsaf dsaf dsafdsaf";
+
+        IElementSerializer elementSerializer = new StandardSerializer();
+
+        IElementAttributes attr = new ElementAttributes();
+        attr.setMaxLife(34);
+
+        ICacheElement<String, String> before = new CacheElement<>( cacheName, key, value );
+        before.setElementAttributes( attr );
+
+        // DO WORK
+        ICacheElementSerialized<String, String> alreadySerialized =
+            SerializationConversionUtil.getSerializedCacheElement( before, elementSerializer );
+        ICacheElementSerialized<String, String> serialized =
+            SerializationConversionUtil.getSerializedCacheElement( alreadySerialized, elementSerializer );
+
+        // VERIFY
+        assertNotNull( "Should have a serialized object.", serialized );
+
+        // DO WORK
+        ICacheElement<String, String> after =
+            SerializationConversionUtil.getDeSerializedCacheElement( serialized, elementSerializer );
+
+        // VERIFY
+        assertNotNull( "Should have a deserialized object.", after );
+        assertEquals( "Values should be the same.", before.getVal(), after.getVal() );
+        assertEquals( "Attributes should be the same.", before.getElementAttributes().getMaxLife(), after
+            .getElementAttributes().getMaxLife() );
+        assertEquals( "Keys should be the same.", before.getKey(), after.getKey() );
+        assertEquals( "Cache name should be the same.", before.getCacheName(), after.getCacheName() );
+    }
+
+    /**
+     * Verify that we get an IOException for a null serializer.
+     */
+    public void testNullSerializerConversion()
+    {
+        // SETUP
+        String cacheName = "testName";
+        String key = "key";
+        String value = "value fdsadf dsafdsa fdsaf dsafdsaf dsafdsaf dsaf dsaf dsaf dsafa dsaf dsaf dsafdsaf";
+
+        IElementSerializer elementSerializer = null;// new StandardSerializer();
+
+        IElementAttributes attr = new ElementAttributes();
+        attr.setMaxLife(34);
+
+        ICacheElement<String, String> before = new CacheElement<>( cacheName, key, value );
+        before.setElementAttributes( attr );
+
+        // DO WORK
+        try
+        {
+            SerializationConversionUtil.getSerializedCacheElement( before, elementSerializer );
+
+            // VERIFY
+            fail( "We should have received an IOException." );
+        }
+        catch ( IOException e )
+        {
+            // expected
+        }
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/serialization/StandardSerializerUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/serialization/StandardSerializerUnitTest.java
new file mode 100644
index 0000000..cfa38ce
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/serialization/StandardSerializerUnitTest.java
@@ -0,0 +1,104 @@
+package org.apache.commons.jcs3.utils.serialization;
+
+import org.apache.commons.jcs3.utils.serialization.StandardSerializer;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the standard serializer.
+ *<p>
+ * @author Aaron Smuts
+ */
+public class StandardSerializerUnitTest
+    extends TestCase
+{
+    /**
+     * Test simple back and forth with a string.
+     *<p>
+     * @throws Exception
+     */
+    public void testSimpleBackAndForth()
+        throws Exception
+    {
+        // SETUP
+        StandardSerializer serializer = new StandardSerializer();
+
+        String before = "adsfdsafdsafdsafdsafdsafdsafdsagfdsafdsafdsfdsafdsafsa333 31231";
+
+        // DO WORK
+        String after = (String) serializer.deSerialize( serializer.serialize( before ), null );
+
+        // VERIFY
+        assertEquals( "Before and after should be the same.", before, after );
+    }
+
+    /**
+     * Test serialization with a null object. Verify that we don't get an error.
+     *<p>
+     * @throws Exception
+     */
+    public void testNullInput()
+        throws Exception
+    {
+        // SETUP
+        StandardSerializer serializer = new StandardSerializer();
+
+        String before = null;
+
+        // DO WORK
+        byte[] serialized = serializer.serialize( before );
+        //System.out.println( "testNullInput " + serialized );
+
+        String after = (String) serializer.deSerialize( serialized, null );
+        //System.out.println( "testNullInput " + after );
+
+        // VERIFY
+        assertNull( "Should have nothing.", after );
+    }
+
+    /**
+     * Test simple back and forth with a string.
+     *<p>
+     * @throws Exception
+     */
+    public void testBigStringBackAndForth()
+        throws Exception
+    {
+        // SETUP
+        StandardSerializer serializer = new StandardSerializer();
+
+        String string = "This is my big string ABCDEFGH";
+        StringBuilder sb = new StringBuilder();
+        sb.append( string );
+        for ( int i = 0; i < 4; i++ )
+        {
+            sb.append( " " + i + sb.toString() ); // big string
+        }
+        String before = sb.toString();
+
+        // DO WORK
+        String after = (String) serializer.deSerialize( serializer.serialize( before ), null );
+
+        // VERIFY
+        assertEquals( "Before and after should be the same.", before, after );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/struct/DoubleLinkedListDumpUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/struct/DoubleLinkedListDumpUnitTest.java
new file mode 100644
index 0000000..2aa4a11
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/struct/DoubleLinkedListDumpUnitTest.java
@@ -0,0 +1,60 @@
+package org.apache.commons.jcs3.utils.struct;
+
+import java.io.StringWriter;
+
+import org.apache.commons.jcs3.TestLogConfigurationUtil;
+import org.apache.commons.jcs3.utils.struct.DoubleLinkedList;
+import org.apache.commons.jcs3.utils.struct.DoubleLinkedListNode;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/** Unit tests for the double linked list. */
+public class DoubleLinkedListDumpUnitTest
+    extends TestCase
+{
+    /** verify that the entries are dumped. */
+    public void testDumpEntries_DebugTrue()
+    {
+        // SETUP
+        StringWriter stringWriter = new StringWriter();
+        TestLogConfigurationUtil.configureLogger( stringWriter, DoubleLinkedList.class.getName() );
+
+        DoubleLinkedList<DoubleLinkedListNode<String>> list = new DoubleLinkedList<>();
+
+        String payload1 = "payload1";
+        DoubleLinkedListNode<String> node1 = new DoubleLinkedListNode<>( payload1 );
+
+        String payload2 = "payload2";
+        DoubleLinkedListNode<String> node2 = new DoubleLinkedListNode<>( payload2 );
+
+        list.addLast( node1 );
+        list.addLast( node2 );
+        list.debugDumpEntries();
+
+        // WO WORK
+        String result = stringWriter.toString();
+
+        // VERIFY
+        assertTrue( "Missing node in log dump", result.indexOf( payload1 ) != -1 );
+        assertTrue( "Missing node in log dump", result.indexOf( payload2 ) != -1 );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/struct/DoubleLinkedListUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/struct/DoubleLinkedListUnitTest.java
new file mode 100644
index 0000000..616ee5e
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/struct/DoubleLinkedListUnitTest.java
@@ -0,0 +1,162 @@
+package org.apache.commons.jcs3.utils.struct;
+
+import org.apache.commons.jcs3.utils.struct.DoubleLinkedList;
+import org.apache.commons.jcs3.utils.struct.DoubleLinkedListNode;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+/** Unit tests for the double linked list. */
+public class DoubleLinkedListUnitTest
+    extends TestCase
+{
+    /** verify that the last is added when the list is empty. */
+    public void testAddLast_Empty()
+    {
+        // SETUP
+        DoubleLinkedList<DoubleLinkedListNode<String>> list = new DoubleLinkedList<>();
+
+        String payload1 = "payload1";
+        DoubleLinkedListNode<String> node1 = new DoubleLinkedListNode<>( payload1 );
+
+        // WO WORK
+        list.addLast( node1 );
+
+        // VERIFY
+        assertEquals( "Wrong last", node1, list.getLast() );
+    }
+
+    /** verify that the last is added when the list is empty. */
+    public void testAddLast_NotEmpty()
+    {
+        // SETUP
+        DoubleLinkedList<DoubleLinkedListNode<String>> list = new DoubleLinkedList<>();
+
+        String payload1 = "payload1";
+        DoubleLinkedListNode<String> node1 = new DoubleLinkedListNode<>( payload1 );
+
+        String payload2 = "payload2";
+        DoubleLinkedListNode<String> node2 = new DoubleLinkedListNode<>( payload2 );
+
+        // WO WORK
+        list.addLast( node1 );
+        list.addLast( node2 );
+
+        // VERIFY
+        assertEquals( "Wrong last", node2, list.getLast() );
+    }
+
+    /** verify that it's added last. */
+    public void testMakeLast_wasFirst()
+    {
+        // SETUP
+        DoubleLinkedList<DoubleLinkedListNode<String>> list = new DoubleLinkedList<>();
+
+        String payload1 = "payload1";
+        DoubleLinkedListNode<String> node1 = new DoubleLinkedListNode<>( payload1 );
+
+        String payload2 = "payload2";
+        DoubleLinkedListNode<String> node2 = new DoubleLinkedListNode<>( payload2 );
+
+        list.addFirst( node2 );
+        list.addFirst(  node1 );
+
+        // DO WORK
+        list.makeLast( node1 );
+
+        // VERIFY
+        assertEquals( "Wrong size", 2, list.size() );
+        assertEquals( "Wrong last", node1, list.getLast() );
+        assertEquals( "Wrong first", node2, list.getFirst() );
+    }
+
+    /** verify that it's added last. */
+    public void testMakeLast_wasLast()
+    {
+        // SETUP
+        DoubleLinkedList<DoubleLinkedListNode<String>> list = new DoubleLinkedList<>();
+
+        String payload1 = "payload1";
+        DoubleLinkedListNode<String> node1 = new DoubleLinkedListNode<>( payload1 );
+
+        String payload2 = "payload2";
+        DoubleLinkedListNode<String> node2 = new DoubleLinkedListNode<>( payload2 );
+
+        list.addFirst( node1 );
+        list.addFirst(  node2 );
+
+        // DO WORK
+        list.makeLast( node1 );
+
+        // VERIFY
+        assertEquals( "Wrong size", 2, list.size() );
+        assertEquals( "Wrong last", node1, list.getLast() );
+        assertEquals( "Wrong first", node2, list.getFirst() );
+    }
+
+    /** verify that it's added last. */
+    public void testMakeLast_wasAlone()
+    {
+        // SETUP
+        DoubleLinkedList<DoubleLinkedListNode<String>> list = new DoubleLinkedList<>();
+
+        String payload1 = "payload1";
+        DoubleLinkedListNode<String> node1 = new DoubleLinkedListNode<>( payload1 );
+
+        list.addFirst( node1 );
+
+        // DO WORK
+        list.makeLast( node1 );
+
+        // VERIFY
+        assertEquals( "Wrong size", 1, list.size() );
+        assertEquals( "Wrong last", node1, list.getLast() );
+        assertEquals( "Wrong first", node1, list.getFirst() );
+    }
+
+    /** verify that it's added last. */
+    public void testMakeLast_wasInMiddle()
+    {
+        // SETUP
+        DoubleLinkedList<DoubleLinkedListNode<String>> list = new DoubleLinkedList<>();
+
+        String payload1 = "payload1";
+        DoubleLinkedListNode<String> node1 = new DoubleLinkedListNode<>( payload1 );
+
+        String payload2 = "payload2";
+        DoubleLinkedListNode<String> node2 = new DoubleLinkedListNode<>( payload2 );
+
+        String payload3 = "payload3";
+        DoubleLinkedListNode<String> node3 = new DoubleLinkedListNode<>( payload3 );
+
+        list.addFirst( node2 );
+        list.addFirst(  node1 );
+        list.addFirst(  node3 );
+
+        // DO WORK
+        list.makeLast( node1 );
+
+        // VERIFY
+        assertEquals( "Wrong size", 3, list.size() );
+        assertEquals( "Wrong last", node1, list.getLast() );
+        assertEquals( "Wrong first", node3, list.getFirst() );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/struct/JCSvsCommonsLRUMapPerformanceTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/struct/JCSvsCommonsLRUMapPerformanceTest.java
new file mode 100644
index 0000000..cfc39a3
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/struct/JCSvsCommonsLRUMapPerformanceTest.java
@@ -0,0 +1,180 @@
+package org.apache.commons.jcs3.utils.struct;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Map;
+
+import org.apache.commons.jcs3.log.Log;
+import org.apache.commons.jcs3.log.LogManager;
+import org.apache.commons.jcs3.utils.struct.LRUMap;
+
+import junit.framework.TestCase;
+
+/**
+ * This ensures that the jcs version of the LRU map is as fast as the commons
+ * version. It has been testing at .6 to .7 times the commons LRU.
+ *
+ */
+public class JCSvsCommonsLRUMapPerformanceTest
+    extends TestCase
+{
+    /** jcs / commons */
+    float ratioPut = 0;
+
+    /** jcs / commons */
+    float ratioGet = 0;
+
+    /** goal */
+    float target = 1.0f;
+
+    /** loops */
+    int loops = 20;
+
+    /** number to test with */
+    int tries = 100000;
+
+    /**
+     * A unit test for JUnit
+     *
+     * @throws Exception
+     *                Description of the Exception
+     */
+    public void testSimpleLoad()
+        throws Exception
+    {
+        Log log = LogManager.getLog( LRUMap.class );
+        if ( log.isDebugEnabled() )
+        {
+            System.out.println( "The log level must be at info or above for the a performance test." );
+            return;
+        }
+
+        doWork();
+        assertTrue( this.ratioPut < target );
+        assertTrue( this.ratioGet < target );
+    }
+
+    /**
+     *
+     */
+    public void doWork()
+    {
+        long start = 0;
+        long end = 0;
+        long time = 0;
+        float tPer = 0;
+
+        long putTotalJCS = 0;
+        long getTotalJCS = 0;
+        long putTotalHashtable = 0;
+        long getTotalHashtable = 0;
+
+        String name = "LRUMap";
+        String cache2Name = "";
+
+        try
+        {
+            Map<String, String> cache = new LRUMap<>( tries );
+
+            for ( int j = 0; j < loops; j++ )
+            {
+                name = "JCS      ";
+                start = System.currentTimeMillis();
+                for ( int i = 0; i < tries; i++ )
+                {
+                    cache.put( "key:" + i, "data" + i );
+                }
+                end = System.currentTimeMillis();
+                time = end - start;
+                putTotalJCS += time;
+                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
+                System.out.println( name + " put time for " + tries + " = " + time + "; millis per = " + tPer );
+
+                start = System.currentTimeMillis();
+                for ( int i = 0; i < tries; i++ )
+                {
+                    cache.get( "key:" + i );
+                }
+                end = System.currentTimeMillis();
+                time = end - start;
+                getTotalJCS += time;
+                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
+                System.out.println( name + " get time for " + tries + " = " + time + "; millis per = " + tPer );
+
+                // /////////////////////////////////////////////////////////////
+                cache2Name = "Commons  ";
+                // or LRUMapJCS
+                Map<String, String> cache2 = new org.apache.commons.collections4.map.LRUMap<>( tries );
+                // cache2Name = "Hashtable";
+                // Hashtable cache2 = new Hashtable();
+                start = System.currentTimeMillis();
+                for ( int i = 0; i < tries; i++ )
+                {
+                    cache2.put( "key:" + i, "data" + i );
+                }
+                end = System.currentTimeMillis();
+                time = end - start;
+                putTotalHashtable += time;
+                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
+                System.out.println( cache2Name + " put time for " + tries + " = " + time + "; millis per = " + tPer );
+
+                start = System.currentTimeMillis();
+                for ( int i = 0; i < tries; i++ )
+                {
+                    cache2.get( "key:" + i );
+                }
+                end = System.currentTimeMillis();
+                time = end - start;
+                getTotalHashtable += time;
+                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
+                System.out.println( cache2Name + " get time for " + tries + " = " + time + "; millis per = " + tPer );
+
+                System.out.println( "\n" );
+            }
+
+        }
+        catch ( Exception e )
+        {
+            e.printStackTrace( System.out );
+            System.out.println( e );
+        }
+
+        long putAvJCS = putTotalJCS / loops;
+        long getAvJCS = getTotalJCS / loops;
+        long putAvHashtable = putTotalHashtable / loops;
+        long getAvHashtable = getTotalHashtable / loops;
+
+        System.out.println( "Finished " + loops + " loops of " + tries + " gets and puts" );
+
+        System.out.println( "\n" );
+        System.out.println( "Put average for LRUMap       = " + putAvJCS );
+        System.out.println( "Put average for " + cache2Name + " = " + putAvHashtable );
+        ratioPut = Float.intBitsToFloat( (int) putAvJCS ) / Float.intBitsToFloat( (int) putAvHashtable );
+        System.out.println( name + " puts took " + ratioPut + " times the " + cache2Name + ", the goal is <" + target
+            + "x" );
+
+        System.out.println( "\n" );
+        System.out.println( "Get average for LRUMap       = " + getAvJCS );
+        System.out.println( "Get average for " + cache2Name + " = " + getAvHashtable );
+        ratioGet = Float.intBitsToFloat( (int) getAvJCS ) / Float.intBitsToFloat( (int) getAvHashtable );
+        System.out.println( name + " gets took " + ratioGet + " times the " + cache2Name + ", the goal is <" + target
+            + "x" );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/struct/LRUMapConcurrentUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/struct/LRUMapConcurrentUnitTest.java
new file mode 100644
index 0000000..06c9829
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/struct/LRUMapConcurrentUnitTest.java
@@ -0,0 +1,284 @@
+package org.apache.commons.jcs3.utils.struct;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Iterator;
+
+import org.apache.commons.jcs3.utils.struct.LRUMap;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Tests the LRUMap
+ *
+ */
+public class LRUMapConcurrentUnitTest
+    extends TestCase
+{
+    /** number to test with */
+    private static int items = 20000;
+
+    /**
+     * Constructor for the TestSimpleLoad object
+     * <p>
+     * @param testName
+     *            Description of the Parameter
+     */
+    public LRUMapConcurrentUnitTest( String testName )
+    {
+        super( testName );
+    }
+
+    /**
+     * A unit test suite for JUnit
+     * <p>
+     * @return The test suite
+     */
+    public static Test suite()
+    {
+        // run the basic tests
+        TestSuite suite = new TestSuite( LRUMapConcurrentUnitTest.class );
+
+        // run concurrent tests
+        final LRUMap<String, String> map = new LRUMap<>( 2000 );
+        suite.addTest( new LRUMapConcurrentUnitTest( "conc1" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runConcurrentPutGetTests( map, 2000 );
+            }
+        } );
+        suite.addTest( new LRUMapConcurrentUnitTest( "conc2" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runConcurrentPutGetTests( map, 2000 );
+            }
+        } );
+        suite.addTest( new LRUMapConcurrentUnitTest( "conc3" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runConcurrentPutGetTests( map, 2000 );
+            }
+        } );
+
+        // run more concurrent tests
+        final int max2 = 20000;
+        final LRUMap<String, String> map2 = new LRUMap<>( max2 );
+        suite.addTest( new LRUMapConcurrentUnitTest( "concB1" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runConcurrentRangeTests( map2, 10000, max2 );
+            }
+        } );
+        suite.addTest( new LRUMapConcurrentUnitTest( "concB1" )
+        {
+            @Override
+            public void runTest()
+                throws Exception
+            {
+                this.runConcurrentRangeTests( map2, 0, 9999 );
+            }
+        } );
+
+        return suite;
+    }
+
+    /**
+     * Just test that we can put, get and remove as expected.
+     * <p>
+     * @throws Exception
+     *                Description of the Exception
+     */
+    public void testSimpleLoad()
+        throws Exception
+    {
+        LRUMap<String, String> map = new LRUMap<>( items );
+
+        for ( int i = 0; i < items; i++ )
+        {
+            map.put( i + ":key", "data" + i );
+        }
+
+        for ( int i = items - 1; i >= 0; i-- )
+        {
+            String res = map.get( i + ":key" );
+            assertNotNull( "[" + i + ":key] should not be null", res );
+        }
+
+        // test removal
+        map.remove( "300:key" );
+        assertNull( map.get( "300:key" ) );
+
+    }
+
+    /**
+     * Just make sure that the LRU functions in he most simple case.
+     *
+     * @throws Exception
+     *                Description of the Exception
+     */
+    public void testLRURemoval()
+        throws Exception
+    {
+        int total = 10;
+        LRUMap<String, String> map = new LRUMap<>( total );
+
+        // put the max in
+        for ( int i = 0; i < total; i++ )
+        {
+            map.put( i + ":key", "data" + i );
+        }
+
+        Iterator<?> it = map.entrySet().iterator();
+        while ( it.hasNext() )
+        {
+            assertNotNull( it.next() );
+        }
+//        System.out.println( map.getStatistics() );
+
+        // get the max out backwards
+        for ( int i = total - 1; i >= 0; i-- )
+        {
+            String res = map.get( i + ":key" );
+            assertNotNull( "[" + i + ":key] should not be null", res );
+        }
+
+//        System.out.println( map.getStatistics() );
+
+        //since we got them backwards the total should be at the end.
+        // add one confirm that total is gone.
+        map.put( ( total ) + ":key", "data" + ( total ) );
+        assertNull( map.get( ( total - 1 ) + ":key" ) );
+
+    }
+
+    /**
+     * @throws Exception
+     */
+    public void testLRURemovalAgain()
+        throws Exception
+    {
+        int total = 10000;
+        LRUMap<String, String> map = new LRUMap<>( total );
+
+        // put the max in
+        for ( int i = 0; i < total * 2; i++ )
+        {
+            map.put( i + ":key", "data" + i );
+        }
+
+        // get the total number, these should be null
+        for ( int i = total - 1; i >= 0; i-- )
+        {
+            assertNull( map.get( i + ":key" ) );
+        }
+
+        // get the total to total *2 items out, these should be found.
+        for ( int i = ( total * 2 ) - 1; i >= total; i-- )
+        {
+            String res = map.get( i + ":key" );
+            assertNotNull( "[" + i + ":key] should not be null", res );
+        }
+
+//        System.out.println( map.getStatistics() );
+    }
+
+    /**
+     * Just make sure that we can put and get concurrently
+     *
+     * @param map
+     * @param items
+     * @throws Exception
+     */
+    public void runConcurrentPutGetTests( LRUMap<String, String> map, int items )
+        throws Exception
+    {
+        for ( int i = 0; i < items; i++ )
+        {
+            map.put( i + ":key", "data" + i );
+        }
+
+        for ( int i = items - 1; i >= 0; i-- )
+        {
+            String res = map.get( i + ":key" );
+            assertNotNull( "[" + i + ":key] should not be null", res );
+        }
+    }
+
+    /**
+     * Put, get, and remove from a range. This should occur at a range that is
+     * not touched by other tests.
+     * <p>
+     * @param map
+     * @param start
+     * @param end
+     * @throws Exception
+     */
+    public void runConcurrentRangeTests( LRUMap<String, String> map, int start, int end )
+        throws Exception
+    {
+        for ( int i = start; i < end; i++ )
+        {
+            map.put( i + ":key", "data" + i );
+        }
+
+        for ( int i = end - 1; i >= start; i-- )
+        {
+            String res = map.get( i + ":key" );
+            assertNotNull( "[" + i + ":key] should not be null", res );
+        }
+
+        // test removal
+        map.remove( start + ":key" );
+        assertNull( map.get( start + ":key" ) );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/struct/LRUMapPerformanceTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/struct/LRUMapPerformanceTest.java
new file mode 100644
index 0000000..ed9a63f
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/struct/LRUMapPerformanceTest.java
@@ -0,0 +1,178 @@
+package org.apache.commons.jcs3.utils.struct;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Map;
+
+import org.apache.commons.jcs3.utils.struct.LRUMap;
+
+import junit.framework.TestCase;
+
+/**
+ * This ensures that the jcs version of the LRU map is as fast as the commons
+ * version. It has been testing at .6 to .7 times the commons LRU.
+ * <p>
+ * @author aaronsm
+ *
+ */
+public class LRUMapPerformanceTest
+    extends TestCase
+{
+    /** The put put ration after the test */
+    float ratioPut = 0;
+
+    /** The ratio after the test */
+    float ratioGet = 0;
+
+    /** put jcs / commons ratio */
+    float targetPut = 1.2f;
+
+    /** get jcs / commons ratio */
+    float targetGet = .5f;
+
+    /** Time to loop */
+    int loops = 20;
+
+    /** items to put and get per loop */
+    int tries = 100000;
+
+    /**
+     * A unit test for JUnit
+     *
+     * @throws Exception
+     *                Description of the Exception
+     */
+    public void testSimpleLoad()
+        throws Exception
+    {
+        doWork();
+        assertTrue( this.ratioPut < targetPut );
+        assertTrue( this.ratioGet < targetGet );
+    }
+
+    /**
+     *
+     */
+    public void doWork()
+    {
+        long start = 0;
+        long end = 0;
+        long time = 0;
+        float tPer = 0;
+
+        long putTotalJCS = 0;
+        long getTotalJCS = 0;
+        long putTotalHashtable = 0;
+        long getTotalHashtable = 0;
+
+        String name = "LRUMap";
+        String cache2Name = "";
+
+        try
+        {
+            LRUMap<String, String> cache = new LRUMap<>( tries );
+
+            for ( int j = 0; j < loops; j++ )
+            {
+                name = "JCS      ";
+                start = System.currentTimeMillis();
+                for ( int i = 0; i < tries; i++ )
+                {
+                    cache.put( "key:" + i, "data" + i );
+                }
+                end = System.currentTimeMillis();
+                time = end - start;
+                putTotalJCS += time;
+                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
+                System.out.println( name + " put time for " + tries + " = " + time + "; millis per = " + tPer );
+
+                start = System.currentTimeMillis();
+                for ( int i = 0; i < tries; i++ )
+                {
+                    cache.get( "key:" + i );
+                }
+                end = System.currentTimeMillis();
+                time = end - start;
+                getTotalJCS += time;
+                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
+                System.out.println( name + " get time for " + tries + " = " + time + "; millis per = " + tPer );
+
+                ///////////////////////////////////////////////////////////////
+                cache2Name = "LRUMap (commons)";
+                //or LRUMapJCS
+                Map<String, String> cache2 = new org.apache.commons.collections4.map.LRUMap<>( tries );
+//                Map<String, String> cache2 = new ConcurrentLinkedHashMap.Builder<String, String>()
+//                        .maximumWeightedCapacity( tries )
+//                        .build();
+                //cache2Name = "Hashtable";
+                //Hashtable cache2 = new Hashtable();
+                start = System.currentTimeMillis();
+                for ( int i = 0; i < tries; i++ )
+                {
+                    cache2.put( "key:" + i, "data" + i );
+                }
+                end = System.currentTimeMillis();
+                time = end - start;
+                putTotalHashtable += time;
+                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
+                System.out.println( cache2Name + " put time for " + tries + " = " + time + "; millis per = " + tPer );
+
+                start = System.currentTimeMillis();
+                for ( int i = 0; i < tries; i++ )
+                {
+                    cache2.get( "key:" + i );
+                }
+                end = System.currentTimeMillis();
+                time = end - start;
+                getTotalHashtable += time;
+                tPer = Float.intBitsToFloat( (int) time ) / Float.intBitsToFloat( tries );
+                System.out.println( cache2Name + " get time for " + tries + " = " + time + "; millis per = " + tPer );
+
+                System.out.println( "\n" );
+            }
+        }
+        catch ( Exception e )
+        {
+            e.printStackTrace( System.out );
+            System.out.println( e );
+        }
+
+        long putAvJCS = putTotalJCS / loops;
+        long getAvJCS = getTotalJCS / loops;
+        long putAvHashtable = putTotalHashtable / loops;
+        long getAvHashtable = getTotalHashtable / loops;
+
+        System.out.println( "Finished " + loops + " loops of " + tries + " gets and puts" );
+
+        System.out.println( "\n" );
+        System.out.println( "Put average for LRUMap       = " + putAvJCS );
+        System.out.println( "Put average for " + cache2Name + " = " + putAvHashtable );
+        ratioPut = Float.intBitsToFloat( (int) putAvJCS ) / Float.intBitsToFloat( (int) putAvHashtable );
+        System.out.println( name + " puts took " + ratioPut + " times the " + cache2Name + ", the goal is <" + targetPut
+            + "x" );
+
+        System.out.println( "\n" );
+        System.out.println( "Get average for LRUMap       = " + getAvJCS );
+        System.out.println( "Get average for " + cache2Name + " = " + getAvHashtable );
+        ratioGet = Float.intBitsToFloat( (int) getAvJCS ) / Float.intBitsToFloat( (int) getAvHashtable );
+        System.out.println( name + " gets took " + ratioGet + " times the " + cache2Name + ", the goal is <" + targetGet
+            + "x" );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/struct/LRUMapUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/struct/LRUMapUnitTest.java
new file mode 100644
index 0000000..c65707f
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/struct/LRUMapUnitTest.java
@@ -0,0 +1,136 @@
+package org.apache.commons.jcs3.utils.struct;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.commons.jcs3.utils.struct.LRUMap;
+
+import java.util.Set;
+
+import junit.framework.TestCase;
+
+/**
+ * Basic unit tests for the LRUMap
+ *
+ * @author Aaron Smuts
+ *
+ */
+public class LRUMapUnitTest
+    extends TestCase
+{
+
+    /**
+     * Put up to the size limit and then make sure they are all there.
+     *
+     */
+    public void testPutWithSizeLimit()
+    {
+        int size = 10;
+        Map<String, String> cache = new LRUMap<>( size );
+
+        for ( int i = 0; i < size; i++ )
+        {
+            cache.put( "key:" + i, "data:" + i );
+        }
+
+        for ( int i = 0; i < size; i++ )
+        {
+            String data = cache.get( "key:" + i );
+            assertEquals( "Data is wrong.", "data:" + i, data );
+        }
+    }
+
+    /**
+     * Put into the lru with no limit and then make sure they are all there.
+     *
+     */
+    public void testPutWithNoSizeLimit()
+    {
+        int size = 10;
+        Map<String, String> cache = new LRUMap<>( );
+
+        for ( int i = 0; i < size; i++ )
+        {
+            cache.put( "key:" + i, "data:" + i );
+        }
+
+        for ( int i = 0; i < size; i++ )
+        {
+            String data = cache.get( "key:" + i );
+            assertEquals( "Data is wrong.", "data:" + i, data );
+        }
+    }
+
+    /**
+     * Put and then remove.  Make sure the element is returned.
+     *
+     */
+    public void testPutAndRemove()
+    {
+        int size = 10;
+        Map<String, String> cache = new LRUMap<>( size );
+
+        cache.put( "key:" + 1, "data:" + 1 );
+        String data = cache.remove( "key:" + 1 );
+        assertEquals( "Data is wrong.", "data:" + 1, data );
+    }
+
+    /**
+     * Call remove on an empty map
+     *
+     */
+    public void testRemoveEmpty()
+    {
+        int size = 10;
+        Map<String, String> cache = new LRUMap<>( size );
+
+        Object returned = cache.remove( "key:" + 1 );
+        assertNull( "Shouldn't hvae anything.", returned );
+    }
+
+
+    /**
+     * Add items to the map and then test to see that they come back in the entry set.
+     *
+     */
+    public void testGetEntrySet()
+    {
+        int size = 10;
+        Map<String, String> cache = new LRUMap<>( size );
+
+        for ( int i = 0; i < size; i++ )
+        {
+            cache.put( "key:" + i, "data:" + i );
+        }
+
+        Set<Entry<String, String>> entries = cache.entrySet();
+        assertEquals( "Set contains the wrong number of items.", size, entries.size() );
+
+        // check minimal correctness
+        for (Entry<String, String> data : entries)
+        {
+            assertTrue( "Data is wrong.", data.getValue().indexOf( "data:") != -1  );
+        }
+    }
+
+
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/threadpool/ThreadPoolManagerUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/threadpool/ThreadPoolManagerUnitTest.java
new file mode 100644
index 0000000..d68a40f
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/threadpool/ThreadPoolManagerUnitTest.java
@@ -0,0 +1,88 @@
+package org.apache.commons.jcs3.utils.threadpool;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+
+import org.apache.commons.jcs3.utils.props.PropertyLoader;
+import org.apache.commons.jcs3.utils.threadpool.ThreadPoolManager;
+
+import junit.framework.TestCase;
+
+/**
+ * Verify that the manager can create pools as intended by the default and
+ * specified file names.
+ *
+ * @author asmuts
+ */
+public class ThreadPoolManagerUnitTest
+    extends TestCase
+{
+
+    /**
+     * Make sure it can load a default cache.ccf file
+     */
+    public void testDefaultConfig()
+    {
+        Properties props = PropertyLoader.loadProperties( "thread_pool.properties" );
+        ThreadPoolManager.setProps( props );
+        ThreadPoolManager mgr = ThreadPoolManager.getInstance();
+        assertNotNull( mgr );
+
+        ExecutorService pool = mgr.getExecutorService( "test1" );
+        assertNotNull( pool );
+    }
+
+    /**
+     * Make sure it can load a certain configuration
+     */
+    public void testSpecialConfig()
+    {
+        Properties props = PropertyLoader.loadProperties( "thread_pool.properties" );
+        ThreadPoolManager.setProps( props );
+        ThreadPoolManager mgr = ThreadPoolManager.getInstance();
+        assertNotNull( mgr );
+
+        ExecutorService pool = mgr.getExecutorService( "aborttest" );
+        assertNotNull( pool );
+    }
+
+    /**
+     * Get a couple pools by name and then see if they are in the list.
+     *
+     */
+    public void testGetPoolNames()
+    {
+        ThreadPoolManager mgr = ThreadPoolManager.getInstance();
+        assertNotNull( mgr );
+
+        String poolName1 = "testGetPoolNames1";
+        mgr.getExecutorService( poolName1 );
+
+        String poolName2 = "testGetPoolNames2";
+        mgr.getExecutorService( poolName2 );
+
+        Set<String> names = mgr.getPoolNames();
+        assertTrue( "Should have name in list.", names.contains( poolName1 ) );
+        assertTrue( "Should have name in list.", names.contains( poolName2 ) );
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/timing/SleepUtil.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/timing/SleepUtil.java
new file mode 100644
index 0000000..fdacaa4
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/timing/SleepUtil.java
@@ -0,0 +1,50 @@
+package org.apache.commons.jcs3.utils.timing;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Utility methods to help deal with thread issues.
+ */
+public class SleepUtil
+{
+    /**
+     * Sleep for a specified duration in milliseconds. This method is a
+     * platform-specific workaround for Windows due to its inability to resolve
+     * durations of time less than approximately 10 - 16 ms.
+     * <p>
+     * @param milliseconds the number of milliseconds to sleep
+     */
+    public static void sleepAtLeast( long milliseconds )
+    {
+        long endTime = System.currentTimeMillis() + milliseconds;
+
+        while ( System.currentTimeMillis() <= endTime )
+        {
+            try
+            {
+                Thread.sleep( milliseconds );
+            }
+            catch ( InterruptedException e )
+            {
+                // TODO - Do something here?
+            }
+        }
+    }
+}
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/zip/CompressionUtilUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/zip/CompressionUtilUnitTest.java
new file mode 100644
index 0000000..fd3c618
--- /dev/null
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/utils/zip/CompressionUtilUnitTest.java
@@ -0,0 +1,99 @@
+package org.apache.commons.jcs3.utils.zip;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.zip.GZIPOutputStream;
+
+import org.apache.commons.jcs3.utils.zip.CompressionUtil;
+
+/** Unit tests for the compression util */
+public class CompressionUtilUnitTest
+    extends TestCase
+{
+    /** Test method for decompressByteArray. */
+    public final void testDecompressByteArray_failure()
+    {
+        try
+        {
+            // DO WORK
+            CompressionUtil.decompressByteArray( null );
+
+            // VERIFY
+            fail( "excepted an IllegalArgumentException" );
+        }
+        catch ( IllegalArgumentException exception )
+        {
+            // expected
+            return;
+        }
+    }
+
+    /**
+     * Test method for decompressByteArray.
+     * <p>
+     * @throws IOException
+     */
+    public final void testCompressDecompressByteArray_success()
+        throws IOException
+    {
+        // SETUP
+        String text = "This is some text to compress, not a lot, just a bit ";
+
+        // DO WORK
+        byte[] compressedText = CompressionUtil.compressByteArray( text.getBytes() );
+        byte[] output = CompressionUtil.decompressByteArray( compressedText );
+
+        // VERIFY
+        String result = new String( output );
+        assertNotNull( "decompressed output stream shouldn't have been null ", output );
+        assertEquals( text, result );
+    }
+
+    /**
+     * Test method for decompressByteArray.
+     * <p>
+     * @throws IOException
+     */
+    public final void testCompressDecompressGzipByteArray_success()
+        throws IOException
+    {
+        // SETUP
+        String text = " This is some text to compress, not a lot, just a bit ";
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        GZIPOutputStream os = new GZIPOutputStream( baos );
+
+        os.write( text.getBytes() );
+        os.flush();
+        os.close();
+
+        // DO WORK
+        byte[] output = CompressionUtil.decompressGzipByteArray( baos.toByteArray() );
+
+        // VERIFY
+        String result = new String( output );
+        assertNotNull( "decompressed output stream shouldn't have been null ", output );
+        assertEquals( text, result );
+    }
+}
diff --git a/commons-jcs-core/src/test/test-conf/TestARCCache.ccf b/commons-jcs-core/src/test/test-conf/TestARCCache.ccf
index bf8c10f..22874bf 100644
--- a/commons-jcs-core/src/test/test-conf/TestARCCache.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestARCCache.ccf
@@ -18,13 +18,13 @@
 # with the memory shrinker on.
 
 jcs.default=
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=10
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.arc.ARCMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.arc.ARCMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=false
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=1
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=true
 jcs.default.elementattributes.MaxLife=600
 jcs.default.elementattributes.IdleTime=1800
diff --git a/commons-jcs-core/src/test/test-conf/TestBDBJEDiskCacheCon.ccf b/commons-jcs-core/src/test/test-conf/TestBDBJEDiskCacheCon.ccf
index 914386e..f3efaec 100644
--- a/commons-jcs-core/src/test/test-conf/TestBDBJEDiskCacheCon.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestBDBJEDiskCacheCon.ccf
@@ -18,46 +18,46 @@
 # a maximum of 100 objects, so objects should get pushed into the disk cache
 
 jcs.default=indexedDiskCache
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=100
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 # SYSTEM GROUP ID CACHE
 jcs.system.groupIdCache=indexedDiskCache
-jcs.system.groupIdCache.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.system.groupIdCache.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.system.groupIdCache.cacheattributes.MaxObjects=10000
-jcs.system.groupIdCache.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.system.groupIdCache.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 # #### CACHE REGIONS FOR TEST
 
 jcs.region.indexedRegion1=indexedDiskCache
-jcs.region.indexedRegion1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.indexedRegion1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.indexedRegion1.cacheattributes.MaxObjects=100
-jcs.region.indexedRegion1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.indexedRegion1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 jcs.region.indexedRegion2=indexedDiskCache
-jcs.region.indexedRegion2.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.indexedRegion2.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.indexedRegion2.cacheattributes.MaxObjects=100
-jcs.region.indexedRegion2.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.indexedRegion2.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 jcs.region.indexedRegion3=indexedDiskCache
-jcs.region.indexedRegion3.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.indexedRegion3.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.indexedRegion3.cacheattributes.MaxObjects=100
-jcs.region.indexedRegion3.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.indexedRegion3.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 jcs.region.indexedRegion4=indexedDiskCache
-jcs.region.indexedRegion4.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.indexedRegion4.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.indexedRegion4.cacheattributes.MaxObjects=100
-jcs.region.indexedRegion4.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.indexedRegion4.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.indexedRegion4.cacheattributes.UseMemoryShrinker=false
 
 
 # #### AUXILIARY CACHES
 
 # Berkeley DB JE
-jcs.auxiliary.indexedDiskCache=org.apache.commons.jcs.auxiliary.disk.bdbje.BDBJECacheFactory
-jcs.auxiliary.indexedDiskCache.attributes=org.apache.commons.jcs.auxiliary.disk.bdbje.BDBJECacheAttributes
+jcs.auxiliary.indexedDiskCache=org.apache.commons.jcs3.auxiliary.disk.bdbje.BDBJECacheFactory
+jcs.auxiliary.indexedDiskCache.attributes=org.apache.commons.jcs3.auxiliary.disk.bdbje.BDBJECacheAttributes
 jcs.auxiliary.indexedDiskCache.attributes.DiskPath=target/test-sandbox/bdbje-disk-cache-conc
 
 # #############################################################
diff --git a/commons-jcs-core/src/test/test-conf/TestBlockDiskCache.ccf b/commons-jcs-core/src/test/test-conf/TestBlockDiskCache.ccf
index 222a693..083f3e5 100644
--- a/commons-jcs-core/src/test/test-conf/TestBlockDiskCache.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestBlockDiskCache.ccf
@@ -18,50 +18,50 @@
 # a maximum of 100 objects, so objects should get pushed into the disk cache
 
 jcs.default=blockDiskCache
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=100
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 # SYSTEM GROUP ID CACHE
 jcs.system.groupIdCache=blockDiskCache
-jcs.system.groupIdCache.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.system.groupIdCache.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.system.groupIdCache.cacheattributes.MaxObjects=10000
-jcs.system.groupIdCache.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.system.groupIdCache.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 # #### CACHE REGIONS FOR TEST
 
 jcs.region.blockRegion1=blockDiskCache
-jcs.region.blockRegion1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.blockRegion1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.blockRegion1.cacheattributes.MaxObjects=100
-jcs.region.blockRegion1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.blockRegion1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 jcs.region.blockRegion2=blockDiskCache
-jcs.region.blockRegion2.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.blockRegion2.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.blockRegion2.cacheattributes.MaxObjects=100
-jcs.region.blockRegion2.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.blockRegion2.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 jcs.region.blockRegion3=blockDiskCache
-jcs.region.blockRegion3.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.blockRegion3.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.blockRegion3.cacheattributes.MaxObjects=100
-jcs.region.blockRegion3.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.blockRegion3.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 jcs.region.blockRegion4=blockDiskCache2
-jcs.region.blockRegion4.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.blockRegion4.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.blockRegion4.cacheattributes.MaxObjects=100
-jcs.region.blockRegion4.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.blockRegion4.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 # #### AUXILIARY CACHES
 
 # Block Disk Cache
-jcs.auxiliary.blockDiskCache=org.apache.commons.jcs.auxiliary.disk.block.BlockDiskCacheFactory
-jcs.auxiliary.blockDiskCache.attributes=org.apache.commons.jcs.auxiliary.disk.block.BlockDiskCacheAttributes
+jcs.auxiliary.blockDiskCache=org.apache.commons.jcs3.auxiliary.disk.block.BlockDiskCacheFactory
+jcs.auxiliary.blockDiskCache.attributes=org.apache.commons.jcs3.auxiliary.disk.block.BlockDiskCacheAttributes
 jcs.auxiliary.blockDiskCache.attributes.DiskPath=target/test-sandbox/block-disk-cache
 
 # Block Disk Cache
-jcs.auxiliary.blockDiskCache2=org.apache.commons.jcs.auxiliary.disk.block.BlockDiskCacheFactory
-jcs.auxiliary.blockDiskCache2.attributes=org.apache.commons.jcs.auxiliary.disk.block.BlockDiskCacheAttributes
+jcs.auxiliary.blockDiskCache2=org.apache.commons.jcs3.auxiliary.disk.block.BlockDiskCacheFactory
+jcs.auxiliary.blockDiskCache2.attributes=org.apache.commons.jcs3.auxiliary.disk.block.BlockDiskCacheAttributes
 jcs.auxiliary.blockDiskCache2.attributes.DiskPath=target/test-sandbox/block-disk-cache2
 
 # #############################################################
diff --git a/commons-jcs-core/src/test/test-conf/TestBlockDiskCacheCon.ccf b/commons-jcs-core/src/test/test-conf/TestBlockDiskCacheCon.ccf
index e12d2ec..fc82444 100644
--- a/commons-jcs-core/src/test/test-conf/TestBlockDiskCacheCon.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestBlockDiskCacheCon.ccf
@@ -18,40 +18,40 @@
 # a maximum of 100 objects, so objects should get pushed into the disk cache
 
 jcs.default=blockDiskCache
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=100
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 
 # #### CACHE REGIONS FOR TEST
 
 jcs.region.blockRegion1=blockDiskCache
-jcs.region.blockRegion1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.blockRegion1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.blockRegion1.cacheattributes.MaxObjects=100
-jcs.region.blockRegion1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.blockRegion1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 jcs.region.blockRegion2=blockDiskCache
-jcs.region.blockRegion2.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.blockRegion2.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.blockRegion2.cacheattributes.MaxObjects=100
-jcs.region.blockRegion2.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.blockRegion2.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 jcs.region.blockRegion3=blockDiskCache
-jcs.region.blockRegion3.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.blockRegion3.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.blockRegion3.cacheattributes.MaxObjects=100
-jcs.region.blockRegion3.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.blockRegion3.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 jcs.region.blockRegion4=blockDiskCache
-jcs.region.blockRegion4.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.blockRegion4.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.blockRegion4.cacheattributes.MaxObjects=100
-jcs.region.blockRegion4.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.blockRegion4.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 # #### AUXILIARY CACHES
 
 # Block Disk Cache
-jcs.auxiliary.blockDiskCache=org.apache.commons.jcs.auxiliary.disk.block.BlockDiskCacheFactory
-jcs.auxiliary.blockDiskCache.attributes=org.apache.commons.jcs.auxiliary.disk.block.BlockDiskCacheAttributes
+jcs.auxiliary.blockDiskCache=org.apache.commons.jcs3.auxiliary.disk.block.BlockDiskCacheFactory
+jcs.auxiliary.blockDiskCache.attributes=org.apache.commons.jcs3.auxiliary.disk.block.BlockDiskCacheAttributes
 jcs.auxiliary.blockDiskCache.attributes.DiskPath=target/test-sandbox/block-disk-cache-conc
 jcs.auxiliary.blockDiskCache.attributes.MaxPurgatorySize=10000
 jcs.auxiliary.blockDiskCache.attributes.MaxKeySize=10000
diff --git a/commons-jcs-core/src/test/test-conf/TestBlockDiskCacheHuge.ccf b/commons-jcs-core/src/test/test-conf/TestBlockDiskCacheHuge.ccf
index 9106539..b2828e4 100644
--- a/commons-jcs-core/src/test/test-conf/TestBlockDiskCacheHuge.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestBlockDiskCacheHuge.ccf
@@ -18,24 +18,24 @@
 # a maximum of 0 objects, so objects should get pushed into the disk cache
 
 jcs.default=blockDiskCache
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=0
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 # #### CACHE REGIONS FOR TEST
 
 jcs.region.blockRegion1=blockDiskCache
-jcs.region.blockRegion1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.blockRegion1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.blockRegion1.cacheattributes.MaxObjects=0
-jcs.region.blockRegion1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.blockRegion1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 # #### AUXILIARY CACHES
 
 # Block Disk Cache
-jcs.auxiliary.blockDiskCache=org.apache.commons.jcs.auxiliary.disk.block.BlockDiskCacheFactory
-jcs.auxiliary.blockDiskCache.attributes=org.apache.commons.jcs.auxiliary.disk.block.BlockDiskCacheAttributes
+jcs.auxiliary.blockDiskCache=org.apache.commons.jcs3.auxiliary.disk.block.BlockDiskCacheFactory
+jcs.auxiliary.blockDiskCache.attributes=org.apache.commons.jcs3.auxiliary.disk.block.BlockDiskCacheAttributes
 jcs.auxiliary.blockDiskCache.attributes.DiskPath=target/test-sandbox/block-disk-cache-huge
 jcs.auxiliary.blockDiskCache.attributes.MaxPurgatorySize=300000
 jcs.auxiliary.blockDiskCache.attributes.MaxKeySize=1000000
diff --git a/commons-jcs-core/src/test/test-conf/TestBlockDiskCacheSteadyLoad.ccf b/commons-jcs-core/src/test/test-conf/TestBlockDiskCacheSteadyLoad.ccf
index 535131d..d2b26e5 100644
--- a/commons-jcs-core/src/test/test-conf/TestBlockDiskCacheSteadyLoad.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestBlockDiskCacheSteadyLoad.ccf
@@ -18,15 +18,15 @@
 
 # DEFAULT CACHE REGION
 jcs.default=DC
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=100
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.DiskUsagePatternName=UPDATE
 
 
 # AVAILABLE AUXILIARY CACHES
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.block.BlockDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.block.BlockDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.block.BlockDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.block.BlockDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=target/test-sandbox/block-steady-load
 jcs.auxiliary.DC.attributes.maxKeySize=20000
 jcs.auxiliary.DC.attributes.blockSizeBytes=5000
diff --git a/commons-jcs-core/src/test/test-conf/TestDiskCache.ccf b/commons-jcs-core/src/test/test-conf/TestDiskCache.ccf
index dfdf941..c00452d 100644
--- a/commons-jcs-core/src/test/test-conf/TestDiskCache.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestDiskCache.ccf
@@ -18,50 +18,50 @@
 # a maximum of 100 objects, so objects should get pushed into the disk cache
 
 jcs.default=indexedDiskCache
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=100
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 # SYSTEM GROUP ID CACHE
 jcs.system.groupIdCache=indexedDiskCache
-jcs.system.groupIdCache.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.system.groupIdCache.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.system.groupIdCache.cacheattributes.MaxObjects=10000
-jcs.system.groupIdCache.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.system.groupIdCache.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 # #### CACHE REGIONS FOR TEST
 
 jcs.region.indexedRegion1=indexedDiskCache
-jcs.region.indexedRegion1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.indexedRegion1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.indexedRegion1.cacheattributes.MaxObjects=100
-jcs.region.indexedRegion1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.indexedRegion1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 jcs.region.indexedRegion2=indexedDiskCache
-jcs.region.indexedRegion2.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.indexedRegion2.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.indexedRegion2.cacheattributes.MaxObjects=100
-jcs.region.indexedRegion2.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.indexedRegion2.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 jcs.region.indexedRegion3=indexedDiskCache
-jcs.region.indexedRegion3.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.indexedRegion3.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.indexedRegion3.cacheattributes.MaxObjects=100
-jcs.region.indexedRegion3.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.indexedRegion3.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 jcs.region.indexedRegion4=indexedDiskCache2
-jcs.region.indexedRegion4.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.indexedRegion4.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.indexedRegion4.cacheattributes.MaxObjects=100
-jcs.region.indexedRegion4.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.indexedRegion4.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 # #### AUXILIARY CACHES
 
 # Indexed Disk Cache
-jcs.auxiliary.indexedDiskCache=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.indexedDiskCache.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.indexedDiskCache=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.indexedDiskCache.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.indexedDiskCache.attributes.DiskPath=target/test-sandbox/indexed-disk-cache
 
 # Indexed Disk Cache
-jcs.auxiliary.indexedDiskCache2=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.indexedDiskCache2.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.indexedDiskCache2=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.indexedDiskCache2.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.indexedDiskCache2.attributes.DiskPath=target/test-sandbox/indexed-disk-cache2
 
 # #############################################################
diff --git a/commons-jcs-core/src/test/test-conf/TestDiskCacheCon.ccf b/commons-jcs-core/src/test/test-conf/TestDiskCacheCon.ccf
index e9b32e6..6aa3384 100644
--- a/commons-jcs-core/src/test/test-conf/TestDiskCacheCon.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestDiskCacheCon.ccf
@@ -18,40 +18,40 @@
 # a maximum of 100 objects, so objects should get pushed into the disk cache
 
 jcs.default=indexedDiskCache
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=100
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 
 # #### CACHE REGIONS FOR TEST
 
 jcs.region.indexedRegion1=indexedDiskCache
-jcs.region.indexedRegion1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.indexedRegion1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.indexedRegion1.cacheattributes.MaxObjects=100
-jcs.region.indexedRegion1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.indexedRegion1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 jcs.region.indexedRegion2=indexedDiskCache
-jcs.region.indexedRegion2.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.indexedRegion2.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.indexedRegion2.cacheattributes.MaxObjects=100
-jcs.region.indexedRegion2.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.indexedRegion2.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 jcs.region.indexedRegion3=indexedDiskCache
-jcs.region.indexedRegion3.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.indexedRegion3.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.indexedRegion3.cacheattributes.MaxObjects=100
-jcs.region.indexedRegion3.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.indexedRegion3.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 jcs.region.indexedRegion4=indexedDiskCache
-jcs.region.indexedRegion4.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.indexedRegion4.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.indexedRegion4.cacheattributes.MaxObjects=100
-jcs.region.indexedRegion4.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.indexedRegion4.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 # #### AUXILIARY CACHES
 
 # Indexed Disk Cache
-jcs.auxiliary.indexedDiskCache=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.indexedDiskCache.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.indexedDiskCache=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.indexedDiskCache.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.indexedDiskCache.attributes.DiskPath=target/test-sandbox/indexed-disk-cache-conc
 jcs.auxiliary.indexedDiskCache.attributes.MaxPurgatorySize=10000
 jcs.auxiliary.indexedDiskCache.attributes.MaxKeySize=10000
diff --git a/commons-jcs-core/src/test/test-conf/TestDiskCacheDefragPerformance.ccf b/commons-jcs-core/src/test/test-conf/TestDiskCacheDefragPerformance.ccf
index dd6b7a6..b0ab3ce 100644
--- a/commons-jcs-core/src/test/test-conf/TestDiskCacheDefragPerformance.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestDiskCacheDefragPerformance.ccf
@@ -18,14 +18,14 @@
 
 # DEFAULT CACHE REGION
 jcs.default=DC
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=100
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 cs.default.cacheattributes.DiskUsagePatterName=UPDATE
 
 # AVAILABLE AUXILIARY CACHES
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=target/test-sandbox/defrag
 jcs.auxiliary.DC.attributes.maxKeySize=10000
 jcs.auxiliary.DC.attributes.MaxPurgatorySize=5000
diff --git a/commons-jcs-core/src/test/test-conf/TestDiskCacheHuge.ccf b/commons-jcs-core/src/test/test-conf/TestDiskCacheHuge.ccf
index a835210..28362b2 100644
--- a/commons-jcs-core/src/test/test-conf/TestDiskCacheHuge.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestDiskCacheHuge.ccf
@@ -18,24 +18,24 @@
 # a maximum of 0 objects, so objects should get pushed into the disk cache
 
 jcs.default=indexedDiskCache
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=0
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 # #### CACHE REGIONS FOR TEST
 
 jcs.region.indexedRegion1=indexedDiskCache
-jcs.region.indexedRegion1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.indexedRegion1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.indexedRegion1.cacheattributes.MaxObjects=0
-jcs.region.indexedRegion1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.indexedRegion1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 # #### AUXILIARY CACHES
 
 # Indexed Disk Cache
-jcs.auxiliary.indexedDiskCache=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.indexedDiskCache.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.indexedDiskCache=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.indexedDiskCache.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.indexedDiskCache.attributes.DiskPath=target/test-sandbox/indexed-disk-cache-conc
 jcs.auxiliary.indexedDiskCache.attributes.MaxPurgatorySize=300000
 jcs.auxiliary.indexedDiskCache.attributes.MaxKeySize=500000
diff --git a/commons-jcs-core/src/test/test-conf/TestDiskCacheNoMemory.ccf b/commons-jcs-core/src/test/test-conf/TestDiskCacheNoMemory.ccf
index 1d3e1e7..a46b984 100644
--- a/commons-jcs-core/src/test/test-conf/TestDiskCacheNoMemory.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestDiskCacheNoMemory.ccf
@@ -18,50 +18,50 @@
 # a maximum of 0 objects, so objects should get pushed into the disk cache
 
 jcs.default=indexedDiskCache
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=0
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 # SYSTEM GROUP ID CACHE
 jcs.system.groupIdCache=indexedDiskCache
-jcs.system.groupIdCache.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.system.groupIdCache.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.system.groupIdCache.cacheattributes.MaxObjects=0
-jcs.system.groupIdCache.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.system.groupIdCache.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 # #### CACHE REGIONS FOR TEST
 
 jcs.region.indexedRegion1=indexedDiskCache
-jcs.region.indexedRegion1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.indexedRegion1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.indexedRegion1.cacheattributes.MaxObjects=0
-jcs.region.indexedRegion1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.indexedRegion1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 jcs.region.indexedRegion2=indexedDiskCache
-jcs.region.indexedRegion2.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.indexedRegion2.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.indexedRegion2.cacheattributes.MaxObjects=0
-jcs.region.indexedRegion2.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.indexedRegion2.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 jcs.region.indexedRegion3=indexedDiskCache
-jcs.region.indexedRegion3.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.indexedRegion3.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.indexedRegion3.cacheattributes.MaxObjects=0
-jcs.region.indexedRegion3.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.indexedRegion3.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 jcs.region.indexedRegion4=indexedDiskCache2
-jcs.region.indexedRegion4.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.indexedRegion4.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.indexedRegion4.cacheattributes.MaxObjects=0
-jcs.region.indexedRegion4.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.indexedRegion4.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 # #### AUXILIARY CACHES
 
 # Indexed Disk Cache
-jcs.auxiliary.indexedDiskCache=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.indexedDiskCache.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.indexedDiskCache=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.indexedDiskCache.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.indexedDiskCache.attributes.DiskPath=target/test-sandbox/indexed-disk-cache-nomemory
 
 # Indexed Disk Cache
-jcs.auxiliary.indexedDiskCache2=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.indexedDiskCache2.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.indexedDiskCache2=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.indexedDiskCache2.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.indexedDiskCache2.attributes.DiskPath=target/test-sandbox/indexed-disk-cache2-nomemory
 
 # #############################################################
diff --git a/commons-jcs-core/src/test/test-conf/TestDiskCacheSteadyLoad.ccf b/commons-jcs-core/src/test/test-conf/TestDiskCacheSteadyLoad.ccf
index 0b5d3ba..f62d291 100644
--- a/commons-jcs-core/src/test/test-conf/TestDiskCacheSteadyLoad.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestDiskCacheSteadyLoad.ccf
@@ -18,15 +18,15 @@
 
 # DEFAULT CACHE REGION
 jcs.default=DC
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=100
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.DiskUsagePatternName=UPDATE
 
 
 # AVAILABLE AUXILIARY CACHES
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=target/test-sandbox/steady-load
 jcs.auxiliary.DC.attributes.maxKeySize=1000
 jcs.auxiliary.DC.attributes.MaxPurgatorySize=1000
diff --git a/commons-jcs-core/src/test/test-conf/TestDiskCacheUsagePattern.ccf b/commons-jcs-core/src/test/test-conf/TestDiskCacheUsagePattern.ccf
index 5f718ae..07c6cb5 100644
--- a/commons-jcs-core/src/test/test-conf/TestDiskCacheUsagePattern.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestDiskCacheUsagePattern.ccf
@@ -18,29 +18,29 @@
 # a maximum of 100 objects, so objects should get pushed into the disk cache
 
 jcs.default=indexedDiskCache
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=100
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 # #### CACHE REGIONS FOR TEST
 jcs.region.Swap=indexedDiskCache
-jcs.region.Swap.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.Swap.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.Swap.cacheattributes.MaxObjects=100
-jcs.region.Swap.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.Swap.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.Swap.cacheattributes.DiskUsagePatternName=SWAP
 
 jcs.region.Update=indexedDiskCache
-jcs.region.Update.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.Update.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.Update.cacheattributes.MaxObjects=100
-jcs.region.Update.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.Update.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.Update.cacheattributes.DiskUsagePatternName=UPDATE
 
 
 # #### AUXILIARY CACHES
 # Indexed Disk Cache
-jcs.auxiliary.indexedDiskCache=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.indexedDiskCache.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.indexedDiskCache=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.indexedDiskCache.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.indexedDiskCache.attributes.DiskPath=target/test-sandbox/indexed-disk-cache-conc
 jcs.auxiliary.indexedDiskCache.attributes.MaxPurgatorySize=10000
 jcs.auxiliary.indexedDiskCache.attributes.MaxKeySize=10000
diff --git a/commons-jcs-core/src/test/test-conf/TestHSQLDiskCache.ccf b/commons-jcs-core/src/test/test-conf/TestHSQLDiskCache.ccf
index e3fe8b9..b75655d 100644
--- a/commons-jcs-core/src/test/test-conf/TestHSQLDiskCache.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestHSQLDiskCache.ccf
@@ -18,13 +18,13 @@
 # a maximum of 100 objects, so objects should get pushed into the disk cache
 
 jcs.default=HSQL
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=0
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=false
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLife=700
 jcs.default.elementattributes.IdleTime=1800
@@ -34,17 +34,17 @@
 
 
 jcs.region.noRemoveAll=HSQL_NORA
-jcs.region.noRemoveAll.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.noRemoveAll.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.noRemoveAll.cacheattributes.MaxObjects=0
-jcs.region.noRemoveAll.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.noRemoveAll.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 
 # #############################################################
 # ################# AUXILIARY CACHES AVAILABLE ################
 # HSQL disk cache
-jcs.auxiliary.HSQL=org.apache.commons.jcs.auxiliary.disk.jdbc.hsql.HSQLDiskCacheFactory
-jcs.auxiliary.HSQL.attributes=org.apache.commons.jcs.auxiliary.disk.jdbc.JDBCDiskCacheAttributes
+jcs.auxiliary.HSQL=org.apache.commons.jcs3.auxiliary.disk.jdbc.hsql.HSQLDiskCacheFactory
+jcs.auxiliary.HSQL.attributes=org.apache.commons.jcs3.auxiliary.disk.jdbc.JDBCDiskCacheAttributes
 jcs.auxiliary.HSQL.attributes.userName=sa
 jcs.auxiliary.HSQL.attributes.password=
 jcs.auxiliary.HSQL.attributes.url=jdbc:hsqldb:target/HSQLDiskCacheUnitTest1
@@ -57,8 +57,8 @@
 jcs.auxiliary.HSQL.attributes.EventQueueType=SINGLE
 
 # HSQL disk cache, doesn't allow remove all
-jcs.auxiliary.HSQL_NORA=org.apache.commons.jcs.auxiliary.disk.jdbc.hsql.HSQLDiskCacheFactory
-jcs.auxiliary.HSQL_NORA.attributes=org.apache.commons.jcs.auxiliary.disk.jdbc.JDBCDiskCacheAttributes
+jcs.auxiliary.HSQL_NORA=org.apache.commons.jcs3.auxiliary.disk.jdbc.hsql.HSQLDiskCacheFactory
+jcs.auxiliary.HSQL_NORA.attributes=org.apache.commons.jcs3.auxiliary.disk.jdbc.JDBCDiskCacheAttributes
 jcs.auxiliary.HSQL_NORA.attributes.userName=sa
 jcs.auxiliary.HSQL_NORA.attributes.password=
 jcs.auxiliary.HSQL_NORA.attributes.url=jdbc:hsqldb:target/HSQLDiskCacheUnitTest2
diff --git a/commons-jcs-core/src/test/test-conf/TestHSQLDiskCacheConcurrent.ccf b/commons-jcs-core/src/test/test-conf/TestHSQLDiskCacheConcurrent.ccf
index 8f2eb68..327c13d 100644
--- a/commons-jcs-core/src/test/test-conf/TestHSQLDiskCacheConcurrent.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestHSQLDiskCacheConcurrent.ccf
@@ -18,13 +18,13 @@
 # a maximum of 100 objects, so objects should get pushed into the disk cache
 
 jcs.default=HSQL
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=0
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=false
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLife=700
 jcs.default.elementattributes.IdleTime=1800
@@ -34,17 +34,17 @@
 
 
 jcs.region.noRemoveAll=HSQL_NORA
-jcs.region.noRemoveAll.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.noRemoveAll.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.noRemoveAll.cacheattributes.MaxObjects=0
-jcs.region.noRemoveAll.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.noRemoveAll.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 
 # #############################################################
 # ################# AUXILIARY CACHES AVAILABLE ################
 # HSQL disk cache
-jcs.auxiliary.HSQL=org.apache.commons.jcs.auxiliary.disk.jdbc.hsql.HSQLDiskCacheFactory
-jcs.auxiliary.HSQL.attributes=org.apache.commons.jcs.auxiliary.disk.jdbc.JDBCDiskCacheAttributes
+jcs.auxiliary.HSQL=org.apache.commons.jcs3.auxiliary.disk.jdbc.hsql.HSQLDiskCacheFactory
+jcs.auxiliary.HSQL.attributes=org.apache.commons.jcs3.auxiliary.disk.jdbc.JDBCDiskCacheAttributes
 jcs.auxiliary.HSQL.attributes.userName=sa
 jcs.auxiliary.HSQL.attributes.password=
 jcs.auxiliary.HSQL.attributes.url=jdbc:hsqldb:target/HSQLDiskCacheUnitTest1
@@ -58,8 +58,8 @@
 jcs.auxiliary.HSQL.attributes.EventQueuePoolName=disk_cache_event_queue
 
 # HSQL disk cache, doesn't allow remove all
-jcs.auxiliary.HSQL_NORA=org.apache.commons.jcs.auxiliary.disk.jdbc.hsql.HSQLDiskCacheFactory
-jcs.auxiliary.HSQL_NORA.attributes=org.apache.commons.jcs.auxiliary.disk.jdbc.JDBCDiskCacheAttributes
+jcs.auxiliary.HSQL_NORA=org.apache.commons.jcs3.auxiliary.disk.jdbc.hsql.HSQLDiskCacheFactory
+jcs.auxiliary.HSQL_NORA.attributes=org.apache.commons.jcs3.auxiliary.disk.jdbc.JDBCDiskCacheAttributes
 jcs.auxiliary.HSQL_NORA.attributes.userName=sa
 jcs.auxiliary.HSQL_NORA.attributes.password=
 jcs.auxiliary.HSQL_NORA.attributes.url=jdbc:hsqldb:target/HSQLDiskCacheUnitTest2
diff --git a/commons-jcs-core/src/test/test-conf/TestJCS-73.ccf b/commons-jcs-core/src/test/test-conf/TestJCS-73.ccf
index c6220bf..ac6248f 100644
--- a/commons-jcs-core/src/test/test-conf/TestJCS-73.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestJCS-73.ccf
@@ -17,21 +17,21 @@
 # Cache configuration for the 'JCSConcurrentCacheAccessUnitTest' test.
 
 jcs.default=CACHE
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=-1
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.DiskUsagePatternName=UPDATE
 jcs.default.cacheattributes.UseMemoryShrinker=true
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=10
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=10
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsRemote=false
 jcs.default.elementattributes.IsLateral=false
 jcs.default.elementattributes.IsSpool=true
 jcs.default.elementattributes.IsEternal=true
 
-jcs.auxiliary.CACHE=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.CACHE.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.CACHE=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.CACHE.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.CACHE.attributes.DiskPath=target/test-sandbox/concurrent_cache
 jcs.auxiliary.CACHE.attributes.MaxPurgatorySize=-1
 jcs.auxiliary.CACHE.attributes.MaxKeySize=-1
diff --git a/commons-jcs-core/src/test/test-conf/TestJCSvHashtablePerf.ccf b/commons-jcs-core/src/test/test-conf/TestJCSvHashtablePerf.ccf
index 5ef882d..a330518 100644
--- a/commons-jcs-core/src/test/test-conf/TestJCSvHashtablePerf.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestJCSvHashtablePerf.ccf
@@ -18,17 +18,17 @@
 # a maximum of 100 objects, so objects should get pushed into the disk cache
 
 jcs.default=
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=100
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 # #### CACHE REGIONS FOR TEST
 
 jcs.region.testCache1=
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=100000
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 # #############################################################
diff --git a/commons-jcs-core/src/test/test-conf/TestJDBCDiskCache.ccf b/commons-jcs-core/src/test/test-conf/TestJDBCDiskCache.ccf
index 6dab1dd..eee8bed 100644
--- a/commons-jcs-core/src/test/test-conf/TestJDBCDiskCache.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestJDBCDiskCache.ccf
@@ -18,13 +18,13 @@
 # a maximum of 100 objects, so objects should get pushed into the disk cache
 
 jcs.default=JDBC
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=100
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=false
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLife=700
 jcs.default.elementattributes.IdleTime=1800
@@ -35,8 +35,8 @@
 # #############################################################
 # ################# AUXILIARY CACHES AVAILABLE ################
 # JDBC disk cache
-jcs.auxiliary.JDBC=org.apache.commons.jcs.auxiliary.disk.jdbc.JDBCDiskCacheFactory
-jcs.auxiliary.JDBC.attributes=org.apache.commons.jcs.auxiliary.disk.jdbc.JDBCDiskCacheAttributes
+jcs.auxiliary.JDBC=org.apache.commons.jcs3.auxiliary.disk.jdbc.JDBCDiskCacheFactory
+jcs.auxiliary.JDBC.attributes=org.apache.commons.jcs3.auxiliary.disk.jdbc.JDBCDiskCacheAttributes
 jcs.auxiliary.JDBC.attributes.userName=sa
 jcs.auxiliary.JDBC.attributes.password=
 jcs.auxiliary.JDBC.attributes.url=jdbc:hsqldb:target/cache_hsql_db
diff --git a/commons-jcs-core/src/test/test-conf/TestJDBCDiskCacheRemoval.ccf b/commons-jcs-core/src/test/test-conf/TestJDBCDiskCacheRemoval.ccf
index b90eb0f..b038ab9 100644
--- a/commons-jcs-core/src/test/test-conf/TestJDBCDiskCacheRemoval.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestJDBCDiskCacheRemoval.ccf
@@ -18,13 +18,13 @@
 # a maximum of 100 objects, so objects should get pushed into the disk cache
 
 jcs.default=JDBC
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=0
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=false
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLife=700
 jcs.default.elementattributes.IdleTime=1800
@@ -35,8 +35,8 @@
 # #############################################################
 # ################# AUXILIARY CACHES AVAILABLE ################
 # JDBC disk cache
-jcs.auxiliary.JDBC=org.apache.commons.jcs.auxiliary.disk.jdbc.JDBCDiskCacheFactory
-jcs.auxiliary.JDBC.attributes=org.apache.commons.jcs.auxiliary.disk.jdbc.JDBCDiskCacheAttributes
+jcs.auxiliary.JDBC=org.apache.commons.jcs3.auxiliary.disk.jdbc.JDBCDiskCacheFactory
+jcs.auxiliary.JDBC.attributes=org.apache.commons.jcs3.auxiliary.disk.jdbc.JDBCDiskCacheAttributes
 jcs.auxiliary.JDBC.attributes.userName=sa
 jcs.auxiliary.JDBC.attributes.password=
 jcs.auxiliary.JDBC.attributes.url=jdbc:hsqldb:target/JDBCDiskCacheRemovalUnitTest
diff --git a/commons-jcs-core/src/test/test-conf/TestJDBCDiskCacheSharedPool.ccf b/commons-jcs-core/src/test/test-conf/TestJDBCDiskCacheSharedPool.ccf
index f73dad5..b40fbcf 100644
--- a/commons-jcs-core/src/test/test-conf/TestJDBCDiskCacheSharedPool.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestJDBCDiskCacheSharedPool.ccf
@@ -18,13 +18,13 @@
 # a maximum of 100 objects, so objects should get pushed into the disk cache
 
 jcs.default=JDBC_0
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=100
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=false
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLife=700
 jcs.default.elementattributes.IdleTime=1800
@@ -41,8 +41,8 @@
 # #############################################################
 # ################# AUXILIARY CACHES AVAILABLE ################
 # JDBC disk cache
-jcs.auxiliary.JDBC_0=org.apache.commons.jcs.auxiliary.disk.jdbc.JDBCDiskCacheFactory
-jcs.auxiliary.JDBC_0.attributes=org.apache.commons.jcs.auxiliary.disk.jdbc.JDBCDiskCacheAttributes
+jcs.auxiliary.JDBC_0=org.apache.commons.jcs3.auxiliary.disk.jdbc.JDBCDiskCacheFactory
+jcs.auxiliary.JDBC_0.attributes=org.apache.commons.jcs3.auxiliary.disk.jdbc.JDBCDiskCacheAttributes
 jcs.auxiliary.JDBC_0.attributes.tableName=JCS_STORE_0
 jcs.auxiliary.JDBC_0.attributes.testBeforeInsert=false
 jcs.auxiliary.JDBC_0.attributes.allowRemoveAll=true
@@ -51,8 +51,8 @@
 jcs.auxiliary.JDBC_0.attributes.EventQueueType=POOLED
 jcs.auxiliary.JDBC_0.attributes.EventQueuePoolName=disk_cache_event_queue
 
-jcs.auxiliary.JDBC_1=org.apache.commons.jcs.auxiliary.disk.jdbc.JDBCDiskCacheFactory
-jcs.auxiliary.JDBC_1.attributes=org.apache.commons.jcs.auxiliary.disk.jdbc.JDBCDiskCacheAttributes
+jcs.auxiliary.JDBC_1=org.apache.commons.jcs3.auxiliary.disk.jdbc.JDBCDiskCacheFactory
+jcs.auxiliary.JDBC_1.attributes=org.apache.commons.jcs3.auxiliary.disk.jdbc.JDBCDiskCacheAttributes
 jcs.auxiliary.JDBC_1.attributes.tableName=JCS_STORE_1
 jcs.auxiliary.JDBC_1.attributes.testBeforeInsert=false
 jcs.auxiliary.JDBC_1.attributes.allowRemoveAll=true
diff --git a/commons-jcs-core/src/test/test-conf/TestJDBCDiskCacheShrink.ccf b/commons-jcs-core/src/test/test-conf/TestJDBCDiskCacheShrink.ccf
index 5360862..b1a03d8 100644
--- a/commons-jcs-core/src/test/test-conf/TestJDBCDiskCacheShrink.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestJDBCDiskCacheShrink.ccf
@@ -18,13 +18,13 @@
 # a maximum of 100 objects, so objects should get pushed into the disk cache
 
 jcs.default=JDBC
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=100
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=false
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLife=700
 jcs.default.elementattributes.IdleTime=1800
@@ -35,29 +35,29 @@
 # #############################################################
 # ################# REGIONS ###################################
 jcs.region.expire1Second=JDBC
-jcs.region.expire1Second.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.expire1Second.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.expire1Second.cacheattributes.MaxObjects=0
-jcs.region.expire1Second.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.expire1Second.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.expire1Second.elementattributes.MaxLife=1
 
 jcs.region.expire100Second=JDBC
-jcs.region.expire100Second.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.expire100Second.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.expire100Second.cacheattributes.MaxObjects=0
-jcs.region.expire100Second.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.expire100Second.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.expire100Second.elementattributes.MaxLife=100
 
 jcs.region.eternal=JDBC
-jcs.region.eternal.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.eternal.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.eternal.cacheattributes.MaxObjects=0
-jcs.region.eternal.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.eternal.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.eternal.elementattributes.MaxLife=1
 jcs.region.eternal.elementattributes.IsEternal=true
 
 # #############################################################
 # ################# AUXILIARY CACHES AVAILABLE ################
 # JDBC disk cache
-jcs.auxiliary.JDBC=org.apache.commons.jcs.auxiliary.disk.jdbc.JDBCDiskCacheFactory
-jcs.auxiliary.JDBC.attributes=org.apache.commons.jcs.auxiliary.disk.jdbc.JDBCDiskCacheAttributes
+jcs.auxiliary.JDBC=org.apache.commons.jcs3.auxiliary.disk.jdbc.JDBCDiskCacheFactory
+jcs.auxiliary.JDBC.attributes=org.apache.commons.jcs3.auxiliary.disk.jdbc.JDBCDiskCacheAttributes
 jcs.auxiliary.JDBC.attributes.userName=sa
 jcs.auxiliary.JDBC.attributes.password=
 jcs.auxiliary.JDBC.attributes.url=jdbc:hsqldb:target/JDBCDiskCacheShrinkUnitTest
diff --git a/commons-jcs-core/src/test/test-conf/TestJispDiskCache.ccf b/commons-jcs-core/src/test/test-conf/TestJispDiskCache.ccf
index 9a135d9..5e3a584 100644
--- a/commons-jcs-core/src/test/test-conf/TestJispDiskCache.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestJispDiskCache.ccf
@@ -18,13 +18,13 @@
 # a maximum of 100 objects, so objects should get pushed into the disk cache
 
 jcs.default=JDC
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=100
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=false
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLife=700
 jcs.default.elementattributes.IdleTime=1800
@@ -34,7 +34,7 @@
 
 # #### AUXILIARY CACHES
 # JISP Disk Cache -- save memory with disk key storage
-jcs.auxiliary.JDC=org.apache.commons.jcs.auxiliary.disk.jisp.JISPCacheFactory
-jcs.auxiliary.JDC.attributes=org.apache.commons.jcs.auxiliary.disk.jisp.JISPCacheAttributes
+jcs.auxiliary.JDC=org.apache.commons.jcs3.auxiliary.disk.jisp.JISPCacheFactory
+jcs.auxiliary.JDC.attributes=org.apache.commons.jcs3.auxiliary.disk.jisp.JISPCacheAttributes
 jcs.auxiliary.JDC.attributes.DiskPath=target/test-sandbox/jjisp-disk-cache
 jcs.auxiliary.JDC.attributes.ClearOnStart=false
diff --git a/commons-jcs-core/src/test/test-conf/TestLHMLRUCache.ccf b/commons-jcs-core/src/test/test-conf/TestLHMLRUCache.ccf
index ba34a49..ce9fe39 100644
--- a/commons-jcs-core/src/test/test-conf/TestLHMLRUCache.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestLHMLRUCache.ccf
@@ -18,6 +18,6 @@
 # a maximum of 100 objects, so objects should get pushed into the disk cache
 
 jcs.default=
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=100
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LHMLRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LHMLRUMemoryCache
diff --git a/commons-jcs-core/src/test/test-conf/TestMRUCache.ccf b/commons-jcs-core/src/test/test-conf/TestMRUCache.ccf
index 941f689..402a433 100644
--- a/commons-jcs-core/src/test/test-conf/TestMRUCache.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestMRUCache.ccf
@@ -18,13 +18,13 @@
 # with the memory shrinker on.
 
 jcs.default=
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=1000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.mru.MRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.mru.MRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=true
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=1
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLife=600
 jcs.default.elementattributes.IdleTime=1800
@@ -34,12 +34,12 @@
 
 # Region defined that uses the MRU
 jcs.region.mruDefined=
-jcs.region.mruDefined.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.mruDefined.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.mruDefined.cacheattributes.MaxObjects=100000
-jcs.region.mruDefined.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.mru.MRUMemoryCache
+jcs.region.mruDefined.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.mru.MRUMemoryCache
 
 # Region defined that uses the LRU
 jcs.region.lruDefined=
-jcs.region.lruDefined.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.lruDefined.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.lruDefined.cacheattributes.MaxObjects=100000
-jcs.region.lruDefined.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.lruDefined.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
diff --git a/commons-jcs-core/src/test/test-conf/TestMySQLDiskCache.ccf b/commons-jcs-core/src/test/test-conf/TestMySQLDiskCache.ccf
index 8f82e2a..7403ceb 100644
--- a/commons-jcs-core/src/test/test-conf/TestMySQLDiskCache.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestMySQLDiskCache.ccf
@@ -20,13 +20,13 @@
 # verify that the mysql disk cache works.
 
 jcs.default=MYSQL
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=0
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=false
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLife=700
 jcs.default.elementattributes.IdleTime=1800
@@ -37,8 +37,8 @@
 # #############################################################
 # ################# AUXILIARY CACHES AVAILABLE ################
 # MYSQL disk cache
-jcs.auxiliary.MYSQL=org.apache.commons.jcs.auxiliary.disk.jdbc.mysql.MySQLDiskCacheFactory
-jcs.auxiliary.MYSQL.attributes=org.apache.commons.jcs.auxiliary.disk.jdbc.mysql.MySQLDiskCacheAttributes
+jcs.auxiliary.MYSQL=org.apache.commons.jcs3.auxiliary.disk.jdbc.mysql.MySQLDiskCacheFactory
+jcs.auxiliary.MYSQL.attributes=org.apache.commons.jcs3.auxiliary.disk.jdbc.mysql.MySQLDiskCacheAttributes
 jcs.auxiliary.MYSQL.attributes.userName=sa
 jcs.auxiliary.MYSQL.attributes.password=
 jcs.auxiliary.MYSQL.attributes.url=jdbc:hsqldb:target/MySQLDiskCacheHsqlBackedUnitTest
diff --git a/commons-jcs-core/src/test/test-conf/TestRemoteCacheClientServer.ccf b/commons-jcs-core/src/test/test-conf/TestRemoteCacheClientServer.ccf
index 224e8ed..0256239 100644
--- a/commons-jcs-core/src/test/test-conf/TestRemoteCacheClientServer.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestRemoteCacheClientServer.ccf
@@ -30,9 +30,9 @@
 # #############################################################
 # ################# DEFAULT CACHE REGION  #####################
 jcs.default=
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=1000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 # #############################################################
diff --git a/commons-jcs-core/src/test/test-conf/TestRemoteCacheEventLogging.ccf b/commons-jcs-core/src/test/test-conf/TestRemoteCacheEventLogging.ccf
index 7c2e399..75d4d23 100644
--- a/commons-jcs-core/src/test/test-conf/TestRemoteCacheEventLogging.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestRemoteCacheEventLogging.ccf
@@ -18,13 +18,13 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=RC
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=200001
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=true
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLife=700
 jcs.default.elementattributes.IdleTime=1800
@@ -33,8 +33,8 @@
 jcs.default.elementattributes.IsLateral=true
 
 
-jcs.auxiliary.RC=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RC.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RC=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RC.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RC.attributes.FailoverServers=localhost:1101
 jcs.auxiliary.RC.attributes.LocalPort=1201
 jcs.auxiliary.RC.attributes.RemoveUponRemotePut=false
@@ -44,4 +44,4 @@
 jcs.auxiliary.RC.attributes.GetTimeoutMillis=-1
 # jcs.auxiliary.RC.attributes.ThreadPoolName=remote_cache_client
 # jcs.auxiliary.RC.attributes.GetOnly=false
-jcs.auxiliary.RC.cacheeventlogger=org.apache.commons.jcs.engine.logging.MockCacheEventLogger
+jcs.auxiliary.RC.cacheeventlogger=org.apache.commons.jcs3.engine.logging.MockCacheEventLogger
diff --git a/commons-jcs-core/src/test/test-conf/TestRemoteCacheServer.ccf b/commons-jcs-core/src/test/test-conf/TestRemoteCacheServer.ccf
index 786ae9f..5880daa 100644
--- a/commons-jcs-core/src/test/test-conf/TestRemoteCacheServer.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestRemoteCacheServer.ccf
@@ -17,6 +17,6 @@
 # #############################################################
 # ################# DEFAULT CACHE REGION  #####################
 jcs.default=
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=1000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
diff --git a/commons-jcs-core/src/test/test-conf/TestRemoteClient.ccf b/commons-jcs-core/src/test/test-conf/TestRemoteClient.ccf
index cb53999..49d87f2 100644
--- a/commons-jcs-core/src/test/test-conf/TestRemoteClient.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestRemoteClient.ccf
@@ -18,13 +18,13 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=RC
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=200001
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=true
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLife=700
 jcs.default.elementattributes.IdleTime=1800
@@ -33,8 +33,8 @@
 jcs.default.elementattributes.IsLateral=true
 
 
-jcs.auxiliary.RC=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RC.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RC=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RC.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RC.attributes.FailoverServers=localhost:1101
 jcs.auxiliary.RC.attributes.LocalPort=1201
 jcs.auxiliary.RC.attributes.RemoveUponRemotePut=false
diff --git a/commons-jcs-core/src/test/test-conf/TestRemoteHttpCache.ccf b/commons-jcs-core/src/test/test-conf/TestRemoteHttpCache.ccf
index 0e0226d..86fbb4c 100644
--- a/commons-jcs-core/src/test/test-conf/TestRemoteHttpCache.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestRemoteHttpCache.ccf
@@ -18,13 +18,13 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=RC
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=0
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=true
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLife=700
 jcs.default.elementattributes.IdleTime=1800
@@ -34,6 +34,6 @@
 
 
 ## The Http Remote Cache Client
-jcs.auxiliary.RC=org.apache.commons.jcs.auxiliary.remote.http.client.RemoteHttpCacheFactory
-jcs.auxiliary.RC.attributes=org.apache.commons.jcs.auxiliary.remote.http.client.RemoteHttpCacheAttributes
+jcs.auxiliary.RC=org.apache.commons.jcs3.auxiliary.remote.http.client.RemoteHttpCacheFactory
+jcs.auxiliary.RC.attributes=org.apache.commons.jcs3.auxiliary.remote.http.client.RemoteHttpCacheAttributes
 jcs.auxiliary.RC.attributes.url=http://localhost:8000/jcs-app/RemoteCache
diff --git a/commons-jcs-core/src/test/test-conf/TestRemoteServer.ccf b/commons-jcs-core/src/test/test-conf/TestRemoteServer.ccf
index 7678d8e..bb4246d 100644
--- a/commons-jcs-core/src/test/test-conf/TestRemoteServer.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestRemoteServer.ccf
@@ -28,14 +28,14 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=200000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=true
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLife=7000
 jcs.default.elementattributes.IdleTime=1800
diff --git a/commons-jcs-core/src/test/test-conf/TestRemoval.ccf b/commons-jcs-core/src/test/test-conf/TestRemoval.ccf
index 5c23490..bb0f410 100644
--- a/commons-jcs-core/src/test/test-conf/TestRemoval.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestRemoval.ccf
@@ -17,19 +17,19 @@
 # JCS Config for unit testing, just a simple memory only cache
 
 jcs.default=
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=1000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 jcs.system.groupIdCache=
-jcs.system.groupIdCache.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.system.groupIdCache.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.system.groupIdCache.cacheattributes.MaxObjects=10000
-jcs.system.groupIdCache.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.system.groupIdCache.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 jcs.region.testCache1=
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=1000
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 # #############################################################
diff --git a/commons-jcs-core/src/test/test-conf/TestSimpleEventHandling.ccf b/commons-jcs-core/src/test/test-conf/TestSimpleEventHandling.ccf
index f2df927..4bcd7a3 100644
--- a/commons-jcs-core/src/test/test-conf/TestSimpleEventHandling.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestSimpleEventHandling.ccf
@@ -18,48 +18,48 @@
 # a maximum of 100 objects, so objects should get pushed into the disk cache
 
 jcs.default=
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=100
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 # #### CACHE REGIONS FOR TEST
 jcs.region.WithDisk=indexedDiskCache
-jcs.region.WithDisk.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.WithDisk.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.WithDisk.cacheattributes.MaxObjects=0
-jcs.region.WithDisk.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.WithDisk.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.WithDisk.elementattributes.IsSpool=true
 
 jcs.region.NoDisk=
-jcs.region.NoDisk.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.NoDisk.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.NoDisk.cacheattributes.MaxObjects=0
-jcs.region.NoDisk.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.NoDisk.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.NoDisk.elementattributes.IsSpool=true
 
 jcs.region.DiskButNotAllowed=indexedDiskCache
-jcs.region.DiskButNotAllowed.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.DiskButNotAllowed.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.DiskButNotAllowed.cacheattributes.MaxObjects=0
-jcs.region.DiskButNotAllowed.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.DiskButNotAllowed.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.DiskButNotAllowed.elementattributes.IsSpool=false
 
 jcs.region.Maxlife=
-jcs.region.Maxlife.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.Maxlife.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.Maxlife.cacheattributes.MaxObjects=200
-jcs.region.Maxlife.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.Maxlife.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.Maxlife.elementattributes.IsEternal=false
 jcs.region.Maxlife.elementattributes.MaxLife=2
 
 jcs.region.Idletime=
-jcs.region.Idletime.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.Idletime.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.Idletime.cacheattributes.MaxObjects=200
-jcs.region.Idletime.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.Idletime.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.Idletime.elementattributes.IsEternal=false
 jcs.region.Idletime.elementattributes.MaxLife=300
 jcs.region.Idletime.elementattributes.IdleTime=1
 
 # #### AUXILIARY CACHES
 # Indexed Disk Cache
-jcs.auxiliary.indexedDiskCache=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.indexedDiskCache.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.indexedDiskCache=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.indexedDiskCache.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.indexedDiskCache.attributes.DiskPath=target/test-sandbox/indexed-disk-cache
 
diff --git a/commons-jcs-core/src/test/test-conf/TestSimpleLoad.ccf b/commons-jcs-core/src/test/test-conf/TestSimpleLoad.ccf
index 6dcef12..b9c1d14 100644
--- a/commons-jcs-core/src/test/test-conf/TestSimpleLoad.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestSimpleLoad.ccf
@@ -17,12 +17,12 @@
 # JCS Config for unit testing, just a simple memory only cache
 
 jcs.default=
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=1000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 jcs.region.testCache1=
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=20001
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
diff --git a/commons-jcs-core/src/test/test-conf/TestSoftReferenceCache.ccf b/commons-jcs-core/src/test/test-conf/TestSoftReferenceCache.ccf
index dbbf718..a259823 100644
--- a/commons-jcs-core/src/test/test-conf/TestSoftReferenceCache.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestSoftReferenceCache.ccf
@@ -17,12 +17,12 @@
 # JCS Config for unit testing
 
 jcs.default=
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=100
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.soft.SoftReferenceMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.soft.SoftReferenceMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=false
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLife=600
 jcs.default.elementattributes.IdleTime=1800
@@ -32,6 +32,6 @@
 
 # Region defined that uses the Soft Reference
 jcs.region.srDefined=
-jcs.region.srDefined.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.srDefined.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.srDefined.cacheattributes.MaxObjects=100000
-jcs.region.srDefined.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.soft.SoftReferenceMemoryCache
+jcs.region.srDefined.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.soft.SoftReferenceMemoryCache
diff --git a/commons-jcs-core/src/test/test-conf/TestSystemProperties.ccf b/commons-jcs-core/src/test/test-conf/TestSystemProperties.ccf
index cd5701a..cc7debc 100644
--- a/commons-jcs-core/src/test/test-conf/TestSystemProperties.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestSystemProperties.ccf
@@ -18,22 +18,22 @@
 # a maximum of 100 objects, so objects should get pushed into the disk cache
 
 jcs.default=indexedDiskCache
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=${MY_SYSTEM_PROPERTY_MAX_SIZE}
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 jcs.region.missing=
-jcs.region.missing.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.missing.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.missing.cacheattributes.MaxObjects=${NO_SUCH_PROPERTY}
-jcs.region.missing.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.missing.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 # #### AUXILIARY CACHES
 
 # Indexed Disk Cache
-jcs.auxiliary.indexedDiskCache=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.indexedDiskCache.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.indexedDiskCache=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.indexedDiskCache.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.indexedDiskCache.attributes.DiskPath=target/test-sandbox/${MY_SYSTEM_PROPERTY_DISK_DIR}
 
 
diff --git a/commons-jcs-core/src/test/test-conf/TestSystemPropertyUsage.ccf b/commons-jcs-core/src/test/test-conf/TestSystemPropertyUsage.ccf
index 9c68eae..f08adb6 100644
--- a/commons-jcs-core/src/test/test-conf/TestSystemPropertyUsage.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestSystemPropertyUsage.ccf
@@ -15,6 +15,6 @@
 # specific language governing permissions and limitations
 # under the License.
 jcs.default=
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=10
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
diff --git a/commons-jcs-core/src/test/test-conf/TestTCPLateralCache.ccf b/commons-jcs-core/src/test/test-conf/TestTCPLateralCache.ccf
index bf6e941..ecda786 100644
--- a/commons-jcs-core/src/test/test-conf/TestTCPLateralCache.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestTCPLateralCache.ccf
@@ -18,24 +18,24 @@
 # a maximum of 100 objects, so objects should get pushed into the disk cache
 
 jcs.default=LTCP
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=10000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 # #### CACHE REGIONS FOR TEST
 
 jcs.region.testTcpRegion1=LTCP
-jcs.region.testTcpRegion1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testTcpRegion1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testTcpRegion1.cacheattributes.MaxObjects=10000
-jcs.region.testTcpRegion1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testTcpRegion1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 # #### AUXILIARY CACHES
 
 # simple Lateral TCP auxiliary
-jcs.auxiliary.LTCP=org.apache.commons.jcs.auxiliary.lateral.socket.tcp.LateralTCPCacheFactory
-jcs.auxiliary.LTCP.attributes=org.apache.commons.jcs.auxiliary.lateral.socket.tcp.TCPLateralCacheAttributes
+jcs.auxiliary.LTCP=org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.LateralTCPCacheFactory
+jcs.auxiliary.LTCP.attributes=org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.TCPLateralCacheAttributes
 jcs.auxiliary.LTCP.attributes.TcpServers=localhost:1111
 jcs.auxiliary.LTCP.attributes.TcpListenerPort=1110
 jcs.auxiliary.LTCP.attributes.TcpListenerHost=localhost
diff --git a/commons-jcs-core/src/test/test-conf/TestTCPLateralCacheConcurrent.ccf b/commons-jcs-core/src/test/test-conf/TestTCPLateralCacheConcurrent.ccf
index 71f4eaa..894ae82 100644
--- a/commons-jcs-core/src/test/test-conf/TestTCPLateralCacheConcurrent.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestTCPLateralCacheConcurrent.ccf
@@ -15,14 +15,14 @@
 # specific language governing permissions and limitations
 # under the License.
 jcs.default=LTCP
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=10000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 # #### AUXILIARY CACHES
 # simple Lateral TCP auxiliary
-jcs.auxiliary.LTCP=org.apache.commons.jcs.auxiliary.lateral.socket.tcp.LateralTCPCacheFactory
-jcs.auxiliary.LTCP.attributes=org.apache.commons.jcs.auxiliary.lateral.socket.tcp.TCPLateralCacheAttributes
+jcs.auxiliary.LTCP=org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.LateralTCPCacheFactory
+jcs.auxiliary.LTCP.attributes=org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.TCPLateralCacheAttributes
 jcs.auxiliary.LTCP.attributes.TransmissionTypeName=TCP
 # jcs.auxiliary.LTCP.attributes.TcpServers=
 jcs.auxiliary.LTCP.attributes.TcpListenerPort=1102
diff --git a/commons-jcs-core/src/test/test-conf/TestTCPLateralIssueRemoveCache.ccf b/commons-jcs-core/src/test/test-conf/TestTCPLateralIssueRemoveCache.ccf
index aee9339..87e41ca 100644
--- a/commons-jcs-core/src/test/test-conf/TestTCPLateralIssueRemoveCache.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestTCPLateralIssueRemoveCache.ccf
@@ -18,24 +18,24 @@
 # a maximum of 100 objects, so objects should get pushed into the disk cache
 
 jcs.default=LTCP
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=10000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 # #### CACHE REGIONS FOR TEST
 
 jcs.region.testTcpRegion1=LTCP
-jcs.region.testTcpRegion1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testTcpRegion1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testTcpRegion1.cacheattributes.MaxObjects=10000
-jcs.region.testTcpRegion1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testTcpRegion1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 # #### AUXILIARY CACHES
 
 # simple Lateral TCP auxiliary
-jcs.auxiliary.LTCP=org.apache.commons.jcs.auxiliary.lateral.socket.tcp.LateralTCPCacheFactory
-jcs.auxiliary.LTCP.attributes=org.apache.commons.jcs.auxiliary.lateral.socket.tcp.TCPLateralCacheAttributes
+jcs.auxiliary.LTCP=org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.LateralTCPCacheFactory
+jcs.auxiliary.LTCP.attributes=org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.TCPLateralCacheAttributes
 jcs.auxiliary.LTCP.attributes.TcpServers=localhost:1117
 jcs.auxiliary.LTCP.attributes.TcpListenerPort=1118
 jcs.auxiliary.LTCP.attributes.AllowGet=false
diff --git a/commons-jcs-core/src/test/test-conf/TestTCPLateralRemoveFilter.ccf b/commons-jcs-core/src/test/test-conf/TestTCPLateralRemoveFilter.ccf
index fdfaca4..d3948fb 100644
--- a/commons-jcs-core/src/test/test-conf/TestTCPLateralRemoveFilter.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestTCPLateralRemoveFilter.ccf
@@ -18,24 +18,24 @@
 # a maximum of 100 objects, so objects should get pushed into the disk cache
 
 jcs.default=LTCP
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=10000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 # #### CACHE REGIONS FOR TEST
 
 jcs.region.testTcpRegion1=LTCP
-jcs.region.testTcpRegion1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testTcpRegion1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testTcpRegion1.cacheattributes.MaxObjects=10000
-jcs.region.testTcpRegion1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testTcpRegion1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 # #### AUXILIARY CACHES
 
 # simple Lateral TCP auxiliary
-jcs.auxiliary.LTCP=org.apache.commons.jcs.auxiliary.lateral.socket.tcp.LateralTCPCacheFactory
-jcs.auxiliary.LTCP.attributes=org.apache.commons.jcs.auxiliary.lateral.socket.tcp.TCPLateralCacheAttributes
+jcs.auxiliary.LTCP=org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.LateralTCPCacheFactory
+jcs.auxiliary.LTCP.attributes=org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.TCPLateralCacheAttributes
 jcs.auxiliary.LTCP.attributes.TcpServers=localhost:1117
 jcs.auxiliary.LTCP.attributes.TcpListenerPort=2001
 jcs.auxiliary.LTCP.attributes.AllowGet=false
diff --git a/commons-jcs-core/src/test/test-conf/TestThrash.ccf b/commons-jcs-core/src/test/test-conf/TestThrash.ccf
index f274d8a..dd0bf35 100644
--- a/commons-jcs-core/src/test/test-conf/TestThrash.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestThrash.ccf
@@ -15,6 +15,6 @@
 # specific language governing permissions and limitations
 # under the License.
 jcs.default=
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=10000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
diff --git a/commons-jcs-core/src/test/test-conf/TestUDPDiscovery.ccf b/commons-jcs-core/src/test/test-conf/TestUDPDiscovery.ccf
index 34b7253..1461d66 100644
--- a/commons-jcs-core/src/test/test-conf/TestUDPDiscovery.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestUDPDiscovery.ccf
@@ -18,13 +18,13 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=LTCP
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=200001
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=true
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLife=700
 jcs.default.elementattributes.IdleTime=1800
@@ -37,28 +37,28 @@
 # ################# CACHE REGIONS AVAILABLE ###################
 # Regions preconfigured for caching
 jcs.region.testCache1=LTCP
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=1000
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache1.cacheattributes.UseMemoryShrinker=true
 jcs.region.testCache1.cacheattributes.ShrinkerIntervalSeconds=30
 jcs.region.testCache1.cacheattributes.MaxMemoryIdleTimeSeconds=300
 jcs.region.testCache1.cacheattributes.MaxSpoolPerRun=100
-jcs.region.testCache1.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache1.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache1.elementattributes.IsEternal=false
 jcs.region.testCache1.elementattributes.MaxLife=60000
 jcs.region.testCache1.elementattributes.IsLateral=true
 jcs.region.testCache1.elementattributes.IsRemote=true
 
 jcs.region.testCache2=LTCP2
-jcs.region.testCache2.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache2.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache2.cacheattributes.MaxObjects=1000
-jcs.region.testCache2.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache2.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache2.cacheattributes.UseMemoryShrinker=true
 jcs.region.testCache2.cacheattributes.ShrinkerIntervalSeconds=30
 jcs.region.testCache2.cacheattributes.MaxMemoryIdleTimeSeconds=300
 jcs.region.testCache2.cacheattributes.MaxSpoolPerRun=100
-jcs.region.testCache2.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache2.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache2.elementattributes.IsEternal=false
 jcs.region.testCache2.elementattributes.MaxLife=60000
 jcs.region.testCache2.elementattributes.IsLateral=true
@@ -68,8 +68,8 @@
 # ################# AUXILIARY CACHES AVAILABLE ################
 
 # Lateral TCp with UDP discovery enabled
-jcs.auxiliary.LTCP=org.apache.commons.jcs.auxiliary.lateral.socket.tcp.LateralTCPCacheFactory
-jcs.auxiliary.LTCP.attributes=org.apache.commons.jcs.auxiliary.lateral.socket.tcp.TCPLateralCacheAttributes
+jcs.auxiliary.LTCP=org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.LateralTCPCacheFactory
+jcs.auxiliary.LTCP.attributes=org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.TCPLateralCacheAttributes
 jcs.auxiliary.LTCP.attributes.TcpServers=
 jcs.auxiliary.LTCP.attributes.TcpListenerPort=1111
 jcs.auxiliary.LTCP.attributes.AllowGet=false
@@ -79,8 +79,8 @@
 # jcs.auxiliary.LTCP.attributes.Receive=true
 
 # Lateral TCP with UDP discovery disabled
-jcs.auxiliary.LTCP2=org.apache.commons.jcs.auxiliary.lateral.socket.tcp.LateralTCPCacheFactory
-jcs.auxiliary.LTCP2.attributes=org.apache.commons.jcs.auxiliary.lateral.socket.tcp.TCPLateralCacheAttributes
+jcs.auxiliary.LTCP2=org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.LateralTCPCacheFactory
+jcs.auxiliary.LTCP2.attributes=org.apache.commons.jcs3.auxiliary.lateral.socket.tcp.TCPLateralCacheAttributes
 jcs.auxiliary.LTCP2.attributes.TcpServers=
 jcs.auxiliary.LTCP2.attributes.TcpListenerPort=1110
 jcs.auxiliary.LTCP2.attributes.AllowGet=false
diff --git a/commons-jcs-core/src/test/test-conf/TestZeroSizeCache.ccf b/commons-jcs-core/src/test/test-conf/TestZeroSizeCache.ccf
index 515cd7c..12faf44 100644
--- a/commons-jcs-core/src/test/test-conf/TestZeroSizeCache.ccf
+++ b/commons-jcs-core/src/test/test-conf/TestZeroSizeCache.ccf
@@ -18,13 +18,13 @@
 # with the memory shrinker on.
 
 jcs.default=
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=0
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=true
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=1
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLife=600
 jcs.default.elementattributes.IdleTime=1800
diff --git a/commons-jcs-core/src/test/test-conf/cache.ccf b/commons-jcs-core/src/test/test-conf/cache.ccf
index 80790e5..ff41e23 100644
--- a/commons-jcs-core/src/test/test-conf/cache.ccf
+++ b/commons-jcs-core/src/test/test-conf/cache.ccf
@@ -18,14 +18,14 @@
 # JCS Config for unit testing, just a simple memory only cache
 
 jcs.default=
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=1000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 jcs.region.testCache1=
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=1000
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
 
 # #############################################################
diff --git a/commons-jcs-core/src/test/test-conf/cache2.ccf b/commons-jcs-core/src/test/test-conf/cache2.ccf
index c263515..2ef1cf6 100644
--- a/commons-jcs-core/src/test/test-conf/cache2.ccf
+++ b/commons-jcs-core/src/test/test-conf/cache2.ccf
@@ -18,13 +18,13 @@
 # ################# DEFAULT CACHE REGION  #####################
 # sets the default aux value for any non configured caches
 jcs.default=DC
-jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.default.cacheattributes.MaxObjects=1000
-jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.default.cacheattributes.UseMemoryShrinker=true
 jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
 jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.default.elementattributes.IsEternal=false
 jcs.default.elementattributes.MaxLife=7
 jcs.default.elementattributes.IdleTime=1800
@@ -35,10 +35,10 @@
 # SYSTEM CACHE
 # should be defined for the storage of group attribute list
 jcs.system.groupIdCache=DC
-jcs.system.groupIdCache.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.system.groupIdCache.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.system.groupIdCache.cacheattributes.MaxObjects=1000
-jcs.system.groupIdCache.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
-jcs.system.groupIdCache.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.system.groupIdCache.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
+jcs.system.groupIdCache.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.system.groupIdCache.elementattributes.IsEternal=true
 jcs.system.groupIdCache.elementattributes.MaxLife=3600
 jcs.system.groupIdCache.elementattributes.IdleTime=1800
@@ -51,26 +51,26 @@
 # ################# CACHE REGIONS AVAILABLE ###################
 # Regions preconfigured for caching
 jcs.region.testCache1=DC,RC
-jcs.region.testCache1.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache1.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache1.cacheattributes.MaxObjects=1000
-jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache1.cacheattributes.UseMemoryShrinker=false
 jcs.region.testCache1.cacheattributes.MaxMemoryIdleTimeSeconds=5000
 jcs.region.testCache1.cacheattributes.MaxSpoolPerRun=100
 jcs.region.testCache1.cacheattributes.ShrinkerIntervalSeconds=30
-jcs.region.testCache1.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache1.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache1.elementattributes.IsEternal=false
 jcs.region.testCache1.elementattributes.MaxLife=6000
 jcs.region.testCache1.elementattributes.IsLateral=true
 
 jcs.region.testCache2=
-jcs.region.testCache2.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache2.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache2.cacheattributes.MaxObjects=100
-jcs.region.testCache2.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache2.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache2.cacheattributes.UseMemoryShrinker=false
 jcs.region.testCache2.cacheattributes.MaxMemoryIdleTimeSeconds=10
 jcs.region.testCache2.cacheattributes.ShrinkerIntervalSeconds=6
-jcs.region.testCache2.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache2.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache2.elementattributes.IsEternal=false
 jcs.region.testCache2.elementattributes.MaxLife=600
 jcs.region.testCache2.elementattributes.IsSpool=true
@@ -78,13 +78,13 @@
 jcs.region.testCache2.elementattributes.IsLateral=true
 
 jcs.region.testCache3=
-jcs.region.testCache3.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes
+jcs.region.testCache3.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
 jcs.region.testCache3.cacheattributes.MaxObjects=100000
-jcs.region.testCache3.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache
+jcs.region.testCache3.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 jcs.region.testCache3.cacheattributes.UseMemoryShrinker=false
 jcs.region.testCache3.cacheattributes.MaxMemoryIdleTimeSeconds=10
 jcs.region.testCache3.cacheattributes.ShrinkerIntervalSeconds=60
-jcs.region.testCache3.elementattributes=org.apache.commons.jcs.engine.ElementAttributes
+jcs.region.testCache3.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes
 jcs.region.testCache3.elementattributes.IsEternal=false
 jcs.region.testCache3.elementattributes.MaxLife=3600
 jcs.region.testCache3.elementattributes.IsSpool=true
@@ -96,51 +96,51 @@
 # ################# AUXILIARY CACHES AVAILABLE ################
 
 # Remote RMI cache without failover
-jcs.auxiliary.RGroup=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RGroup.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RGroup=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RGroup.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RGroup.attributes.RemoteTypeName=LOCAL
 jcs.auxiliary.RGroup.attributes.RemoteHost=localhost
 jcs.auxiliary.RGroup.attributes.RemotePort=1102
 jcs.auxiliary.RGroup.attributes.GetOnly=true
 
 # Remote RMI Cache set up to failover
-jcs.auxiliary.RFailover=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RFailover.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RFailover=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RFailover.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RFailover.attributes.RemoteTypeName=LOCAL
 jcs.auxiliary.RFailover.attributes.FailoverServers=localhost:1102
 jcs.auxiliary.RFailover.attributes.GetOnly=false
 
 # Primary Disk Cache-- faster than the rest because of memory key storage
-jcs.auxiliary.DC=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
-jcs.auxiliary.DC.attributes=org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
+jcs.auxiliary.DC=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheFactory
+jcs.auxiliary.DC.attributes=org.apache.commons.jcs3.auxiliary.disk.indexed.IndexedDiskCacheAttributes
 jcs.auxiliary.DC.attributes.DiskPath=target/test-sandbox/raf
 # new disk cache parameter.
 jcs.auxiliary.DC.attributes.maxKeySize=100000
 jcs.auxiliary.DC.attributes.optimizeAtRemoveCount=300
 
 # Berkeley DB JE
-jcs.auxiliary.JE=org.apache.commons.jcs.auxiliary.disk.bdbje.BDBJECacheFactory
-jcs.auxiliary.JE.attributes=org.apache.commons.jcs.auxiliary.disk.bdbje.BDBJECacheAttributes
+jcs.auxiliary.JE=org.apache.commons.jcs3.auxiliary.disk.bdbje.BDBJECacheFactory
+jcs.auxiliary.JE.attributes=org.apache.commons.jcs3.auxiliary.disk.bdbje.BDBJECacheAttributes
 jcs.auxiliary.JE.attributes.DiskPath=target/test-sandbox/bdbje-disk-cache-conc
 # the minimum cache size is 1024
 jcs.auxiliary.indexedDiskCache.attributes.CacheSize=1024
 # jcs.auxiliary.indexedDiskCache.attributes.CachePercent=0
 
 # HSQL Disk Cache -- too slow as is
-jcs.auxiliary.HDC=org.apache.commons.jcs.auxiliary.disk.hsql.HSQLCacheFactory
-jcs.auxiliary.HDC.attributes=org.apache.commons.jcs.auxiliary.disk.hsql.HSQLCacheAttributes
+jcs.auxiliary.HDC=org.apache.commons.jcs3.auxiliary.disk.hsql.HSQLCacheFactory
+jcs.auxiliary.HDC.attributes=org.apache.commons.jcs3.auxiliary.disk.hsql.HSQLCacheAttributes
 jcs.auxiliary.HDC.attributes.DiskPath=@project_home_f@hsql
 
 # JISP Disk Cache -- save memory with disk key storage
-jcs.auxiliary.JDC=org.apache.commons.jcs.auxiliary.disk.jisp.JISPCacheFactory
-jcs.auxiliary.JDC.attributes=org.apache.commons.jcs.auxiliary.disk.jisp.JISPCacheAttributes
+jcs.auxiliary.JDC=org.apache.commons.jcs3.auxiliary.disk.jisp.JISPCacheFactory
+jcs.auxiliary.JDC.attributes=org.apache.commons.jcs3.auxiliary.disk.jisp.JISPCacheAttributes
 jcs.auxiliary.JDC.attributes.DiskPath=@project_home_f@raf
 jcs.auxiliary.JDC.attributes.ClearOnStart=false
 
 # need to make put or invalidate an option
 # just a remove lock to add
-jcs.auxiliary.RC=org.apache.commons.jcs.auxiliary.remote.RemoteCacheFactory
-jcs.auxiliary.RC.attributes=org.apache.commons.jcs.auxiliary.remote.RemoteCacheAttributes
+jcs.auxiliary.RC=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheFactory
+jcs.auxiliary.RC.attributes=org.apache.commons.jcs3.auxiliary.remote.RemoteCacheAttributes
 jcs.auxiliary.RC.attributes.RemoteHost=10.21.209.150
 jcs.auxiliary.RC.attributes.RemotePort=1102
 # jcs.auxiliary.RC.attributes.LocalPort=1103
@@ -152,42 +152,42 @@
 jcs.auxiliary.RC.attributes.ThreadPoolName=remote_cache_client
 
 # unreliable
-jcs.auxiliary.LUDP=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LUDP.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LUDP=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LUDP.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LUDP.attributes.TransmissionTypeName=UDP
 jcs.auxiliary.LUDP.attributes.UdpMulticastAddr=228.5.6.7
 jcs.auxiliary.LUDP.attributes.UdpMulticastPort=6789
 
-jcs.auxiliary.LJG=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LJG.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LJG=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LJG.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LJG.attributes.TransmissionTypeName=JAVAGROUPS
 jcs.auxiliary.LJG.attributes.PutOnlyMode=true
 jcs.auxiliary.LJG.attributes.JGChannelProperties = UDP(mcast_addr=224.0.0.100;mcast_port=751):PING(timeout=3000):FD:STABLE:NAKACK:UNICAST:FRAG:FLUSH:GMS:VIEW_ENFORCER:QUEUE
 
 
-jcs.auxiliary.JG = org.apache.commons.jcs.auxiliary.javagroups.JavaGroupsCacheFactory
-jcs.auxiliary.JG.attributes = org.apache.commons.jcs.auxiliary.javagroups.JavaGroupsCacheAttributes
+jcs.auxiliary.JG = org.apache.commons.jcs3.auxiliary.javagroups.JavaGroupsCacheFactory
+jcs.auxiliary.JG.attributes = org.apache.commons.jcs3.auxiliary.javagroups.JavaGroupsCacheAttributes
 jcs.auxiliary.JG.attributes.ChannelFactoryClassName = org.javagroups.JChannelFactory
 jcs.auxiliary.JG.attributes.ChannelProperties = UDP(mcast_addr=224.0.0.100;mcast_port=7501):PING:FD:STABLE:NAKACK:UNICAST:FRAG:FLUSH:GMS:VIEW_ENFORCER:QUEUE
 
 
 # almost complete
-jcs.auxiliary.LTCP=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LTCP.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LTCP=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LTCP.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LTCP.attributes.TransmissionTypeName=TCP
 jcs.auxiliary.LTCP.attributes.TcpServers=localhost:1112
 jcs.auxiliary.LTCP.attributes.TcpListenerPort=1111
 jcs.auxiliary.LTCP.attributes.PutOnlyMode=false
 
-jcs.auxiliary.LTCP2=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LTCP2.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LTCP2=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LTCP2.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LTCP2.attributes.TransmissionTypeName=TCP
 jcs.auxiliary.LTCP2.attributes.TcpServers=localhost:1112
 jcs.auxiliary.LTCP2.attributes.TcpListenerPort=1111
 jcs.auxiliary.LTCP2.attributes.PutOnlyMode=true
 
-jcs.auxiliary.XMLRPC=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.XMLRPC.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.XMLRPC=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.XMLRPC.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.XMLRPC.attributes.TransmissionTypeName=XMLRPC
 jcs.auxiliary.XMLRPC.attributes.HttpServers=localhost:8182
 jcs.auxiliary.XMLRPC.attributes.HttpListenerPort=8181
@@ -196,8 +196,8 @@
 
 # example of how to configure the http version of the lateral cache
 # not converteed to new cache
-jcs.auxiliary.LCHTTP=org.apache.commons.jcs.auxiliary.lateral.LateralCacheFactory
-jcs.auxiliary.LCHTTP.attributes=org.apache.commons.jcs.auxiliary.lateral.LateralCacheAttributes
+jcs.auxiliary.LCHTTP=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheFactory
+jcs.auxiliary.LCHTTP.attributes=org.apache.commons.jcs3.auxiliary.lateral.LateralCacheAttributes
 jcs.auxiliary.LCHTTP.attributes.TransmissionType=HTTP
 jcs.auxiliary.LCHTTP.attributes.httpServers=localhost:8080,localhost:7001,localhost:80
 jcs.auxiliary.LCHTTP.attributes.httpReceiveServlet=/cache/LateralCacheReceiverServlet
diff --git a/commons-jcs-core/src/test/test-conf/log4j2-test.xml b/commons-jcs-core/src/test/test-conf/log4j2-test.xml
index 2fc4fac..9bed1fd 100644
--- a/commons-jcs-core/src/test/test-conf/log4j2-test.xml
+++ b/commons-jcs-core/src/test/test-conf/log4j2-test.xml
@@ -24,7 +24,7 @@
         </File>
     </Appenders>
     <Loggers>
-        <Logger name="org.apache.commons.jcs" additivity="false" level="INFO">
+        <Logger name="org.apache.commons.jcs3" additivity="false" level="INFO">
             <AppenderRef ref="jcs"/>
         </Logger> 
         <Root level="ERROR"><!-- log4j 1.2 has DEBUG -->
diff --git a/commons-jcs-dist/pom.xml b/commons-jcs-dist/pom.xml
index a1acab2..598a033 100644
--- a/commons-jcs-dist/pom.xml
+++ b/commons-jcs-dist/pom.xml
@@ -19,13 +19,13 @@
 -->
 <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/xsd/maven-4.0.0.xsd">
   <parent>
-    <artifactId>commons-jcs</artifactId>
+    <artifactId>commons-jcs3</artifactId>
     <groupId>org.apache.commons</groupId>
     <version>3.0-SNAPSHOT</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
 
-  <artifactId>commons-jcs-dist</artifactId>
+  <artifactId>commons-jcs3-dist</artifactId>
   <version>3.0-SNAPSHOT</version>
   <packaging>pom</packaging>
   <name>Apache Commons JCS :: Distribution</name>
@@ -43,7 +43,7 @@
   <dependencies>
     <dependency>
       <groupId>org.apache.commons</groupId>
-      <artifactId>commons-jcs-core</artifactId>
+      <artifactId>commons-jcs3-core</artifactId>
       <version>${project.version}</version>
     </dependency>
   </dependencies>
diff --git a/commons-jcs-dist/src/assembly/bin.xml b/commons-jcs-dist/src/assembly/bin.xml
index 9f56c1f..22ba2e1 100644
--- a/commons-jcs-dist/src/assembly/bin.xml
+++ b/commons-jcs-dist/src/assembly/bin.xml
@@ -25,11 +25,11 @@
     <moduleSet>
       <useAllReactorProjects>true</useAllReactorProjects>
       <includes>
-        <include>org.apache.commons:commons-jcs-core</include>
-        <include>org.apache.commons:commons-jcs-jcache</include>
-        <include>org.apache.commons:commons-jcs-jcache-tck</include>
-        <include>org.apache.commons:commons-jcs-jcache-extras</include>
-        <include>org.apache.commons:commons-jcs-jcache-openjpa</include>
+        <include>org.apache.commons:commons-jcs3-core</include>
+        <include>org.apache.commons:commons-jcs3-jcache</include>
+        <include>org.apache.commons:commons-jcs3-jcache-tck</include>
+        <include>org.apache.commons:commons-jcs3-jcache-extras</include>
+        <include>org.apache.commons:commons-jcs3-jcache-openjpa</include>
       </includes>
       <binaries>
         <includeDependencies>false</includeDependencies>
diff --git a/commons-jcs-dist/src/assembly/src.xml b/commons-jcs-dist/src/assembly/src.xml
index 03cfb2a..f2780b9 100644
--- a/commons-jcs-dist/src/assembly/src.xml
+++ b/commons-jcs-dist/src/assembly/src.xml
@@ -25,12 +25,12 @@
     <moduleSet>
       <useAllReactorProjects>true</useAllReactorProjects>
       <includes>
-        <include>org.apache.commons:commons-jcs-core</include>
-        <include>org.apache.commons:commons-jcs-dist</include>
-        <include>org.apache.commons:commons-jcs-jcache</include>
-        <include>org.apache.commons:commons-jcs-jcache-tck</include>
-        <include>org.apache.commons:commons-jcs-jcache-extras</include>
-        <include>org.apache.commons:commons-jcs-jcache-openjpa</include>
+        <include>org.apache.commons:commons-jcs3-core</include>
+        <include>org.apache.commons:commons-jcs3-dist</include>
+        <include>org.apache.commons:commons-jcs3-jcache</include>
+        <include>org.apache.commons:commons-jcs3-jcache-tck</include>
+        <include>org.apache.commons:commons-jcs3-jcache-extras</include>
+        <include>org.apache.commons:commons-jcs3-jcache-openjpa</include>
       </includes>
       <sources>
         <outputDirectoryMapping>${module.basedir.name}</outputDirectoryMapping>
diff --git a/commons-jcs-jcache-extras/pom.xml b/commons-jcs-jcache-extras/pom.xml
index 9031fa4..55a5379 100644
--- a/commons-jcs-jcache-extras/pom.xml
+++ b/commons-jcs-jcache-extras/pom.xml
@@ -19,13 +19,13 @@
 -->
 <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/xsd/maven-4.0.0.xsd">
   <parent>
-    <artifactId>commons-jcs</artifactId>
+    <artifactId>commons-jcs3</artifactId>
     <groupId>org.apache.commons</groupId>
     <version>3.0-SNAPSHOT</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
 
-  <artifactId>commons-jcs-jcache-extras</artifactId>
+  <artifactId>commons-jcs3-jcache-extras</artifactId>
   <version>3.0-SNAPSHOT</version>
   <name>Apache Commons JCS :: JCache Extras</name>
 
@@ -73,7 +73,7 @@
     </dependency>
     <dependency>
       <groupId>org.apache.commons</groupId>
-      <artifactId>commons-jcs-jcache</artifactId>
+      <artifactId>commons-jcs3-jcache</artifactId>
       <scope>test</scope>
     </dependency>
     <dependency>
diff --git a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/cdi/AnyLiteral.java b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/cdi/AnyLiteral.java
deleted file mode 100644
index 9f9459d..0000000
--- a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/cdi/AnyLiteral.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.extras.cdi;
-
-import javax.enterprise.inject.Any;
-import javax.enterprise.util.AnnotationLiteral;
-
-public class AnyLiteral extends AnnotationLiteral<Any> implements Any
-{
-    public static final AnyLiteral INSTANCE = new AnyLiteral();
-
-    @Override
-    public String toString()
-    {
-        return "@javax.enterprise.inject.Any()";
-    }
-}
diff --git a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/cdi/CacheManagerBean.java b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/cdi/CacheManagerBean.java
deleted file mode 100644
index 68a9fa6..0000000
--- a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/cdi/CacheManagerBean.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.extras.cdi;
-
-import javax.cache.CacheManager;
-import javax.enterprise.context.ApplicationScoped;
-import javax.enterprise.context.spi.CreationalContext;
-import javax.enterprise.inject.spi.Bean;
-import javax.enterprise.inject.spi.InjectionPoint;
-import javax.enterprise.inject.spi.PassivationCapable;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Type;
-import java.util.HashSet;
-import java.util.Set;
-
-import static java.util.Collections.emptySet;
-
-public class CacheManagerBean implements Bean<CacheManager>, PassivationCapable
-{
-    private final Set<Type> types;
-    private final Set<Annotation> qualifiers;
-    private final CacheManager manager;
-    private final String id;
-
-    public CacheManagerBean(final CacheManager cacheManager)
-    {
-        manager = cacheManager;
-        id = getClass().getName() + "-" + hashCode();
-
-        types = new HashSet<Type>();
-        types.add(CacheManager.class);
-        types.add(Object.class);
-
-        qualifiers = new HashSet<Annotation>();
-        qualifiers.add(DefaultLiteral.INSTANCE);
-        qualifiers.add(AnyLiteral.INSTANCE);
-    }
-
-    @Override
-    public Set<Type> getTypes()
-    {
-        return types;
-    }
-
-    @Override
-    public Set<Annotation> getQualifiers()
-    {
-        return qualifiers;
-    }
-
-    @Override
-    public Class<? extends Annotation> getScope()
-    {
-        return ApplicationScoped.class;
-    }
-
-    @Override
-    public String getName()
-    {
-        return null;
-    }
-
-    @Override
-    public boolean isNullable()
-    {
-        return false;
-    }
-
-    @Override
-    public Set<InjectionPoint> getInjectionPoints()
-    {
-        return emptySet();
-    }
-
-    @Override
-    public Class<?> getBeanClass()
-    {
-        return CacheManager.class;
-    }
-
-    @Override
-    public Set<Class<? extends Annotation>> getStereotypes()
-    {
-        return emptySet();
-    }
-
-    @Override
-    public boolean isAlternative()
-    {
-        return false;
-    }
-
-    @Override
-    public CacheManager create(CreationalContext<CacheManager> cacheManagerCreationalContext)
-    {
-        return manager;
-    }
-
-    @Override
-    public void destroy(CacheManager cacheManager, CreationalContext<CacheManager> cacheManagerCreationalContext)
-    {
-        manager.close();
-    }
-
-    @Override
-    public String getId()
-    {
-        return id;
-    }
-}
diff --git a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/cdi/CacheProviderBean.java b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/cdi/CacheProviderBean.java
deleted file mode 100644
index f520664..0000000
--- a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/cdi/CacheProviderBean.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.extras.cdi;
-
-import javax.cache.spi.CachingProvider;
-import javax.enterprise.context.ApplicationScoped;
-import javax.enterprise.context.spi.CreationalContext;
-import javax.enterprise.inject.spi.Bean;
-import javax.enterprise.inject.spi.InjectionPoint;
-import javax.enterprise.inject.spi.PassivationCapable;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Type;
-import java.util.HashSet;
-import java.util.Set;
-
-import static java.util.Collections.emptySet;
-
-public class CacheProviderBean implements Bean<CachingProvider>, PassivationCapable
-{
-    private final Set<Type> types;
-    private final Set<Annotation> qualifiers;
-    private final CachingProvider provider;
-    private final String id;
-
-    public CacheProviderBean(final CachingProvider cacheManager)
-    {
-        provider = cacheManager;
-        id = getClass().getName() + "-" + hashCode();
-
-        types = new HashSet<Type>();
-        types.add(CachingProvider.class);
-        types.add(Object.class);
-
-        qualifiers = new HashSet<Annotation>();
-        qualifiers.add(DefaultLiteral.INSTANCE);
-        qualifiers.add(AnyLiteral.INSTANCE);
-    }
-
-    @Override
-    public Set<Type> getTypes()
-    {
-        return types;
-    }
-
-    @Override
-    public Set<Annotation> getQualifiers()
-    {
-        return qualifiers;
-    }
-
-    @Override
-    public Class<? extends Annotation> getScope()
-    {
-        return ApplicationScoped.class;
-    }
-
-    @Override
-    public String getName()
-    {
-        return null;
-    }
-
-    @Override
-    public boolean isNullable()
-    {
-        return false;
-    }
-
-    @Override
-    public Set<InjectionPoint> getInjectionPoints()
-    {
-        return emptySet();
-    }
-
-    @Override
-    public Class<?> getBeanClass()
-    {
-        return CachingProvider.class;
-    }
-
-    @Override
-    public Set<Class<? extends Annotation>> getStereotypes()
-    {
-        return emptySet();
-    }
-
-    @Override
-    public boolean isAlternative()
-    {
-        return false;
-    }
-
-    @Override
-    public CachingProvider create(final CreationalContext<CachingProvider> cacheManagerCreationalContext)
-    {
-        return provider;
-    }
-
-    @Override
-    public void destroy(final CachingProvider cacheProvider, final CreationalContext<CachingProvider> cacheManagerCreationalContext)
-    {
-        provider.close();
-    }
-
-    @Override
-    public String getId()
-    {
-        return id;
-    }
-}
diff --git a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/cdi/DefaultLiteral.java b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/cdi/DefaultLiteral.java
deleted file mode 100644
index aebe476..0000000
--- a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/cdi/DefaultLiteral.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.extras.cdi;
-
-import javax.enterprise.inject.Default;
-import javax.enterprise.util.AnnotationLiteral;
-
-public class DefaultLiteral extends AnnotationLiteral<Default> implements Default
-{
-    public static final DefaultLiteral INSTANCE = new DefaultLiteral();
-
-    @Override
-    public String toString()
-    {
-        return "@javax.enterprise.inject.Default()";
-    }
-}
diff --git a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/cdi/ExtraJCacheExtension.java b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/cdi/ExtraJCacheExtension.java
deleted file mode 100644
index 225abeb..0000000
--- a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/cdi/ExtraJCacheExtension.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.extras.cdi;
-
-import javax.cache.CacheManager;
-import javax.cache.Caching;
-import javax.cache.spi.CachingProvider;
-import javax.enterprise.event.Observes;
-import javax.enterprise.inject.spi.AfterBeanDiscovery;
-import javax.enterprise.inject.spi.Bean;
-import javax.enterprise.inject.spi.BeforeShutdown;
-import javax.enterprise.inject.spi.Extension;
-import javax.enterprise.inject.spi.ProcessBean;
-import java.util.Properties;
-
-// add default CacheProvider and CacheManager
-public class ExtraJCacheExtension implements Extension
-{
-    private static final boolean ACTIVATED = "true".equals(System.getProperty("org.apache.jcs.extra.cdi", "true"));
-
-    private boolean cacheManagerFound = false;
-    private boolean cacheProviderFound = false;
-    private CacheManager cacheManager;
-    private CachingProvider cachingProvider;
-
-    public <A> void processBean(final @Observes ProcessBean<A> processBeanEvent)
-    {
-        if (!ACTIVATED)
-        {
-            return;
-        }
-
-        if (cacheManagerFound && cacheProviderFound)
-        {
-            return;
-        }
-
-        final Bean<A> bean = processBeanEvent.getBean();
-        if (CacheManagerBean.class.isInstance(bean) || CacheProviderBean.class.isInstance(bean))
-        {
-            return;
-        }
-
-        if (!cacheManagerFound)
-        {
-            cacheManagerFound = bean.getTypes().contains(CacheManager.class);
-        }
-        if (!cacheProviderFound)
-        {
-            cacheProviderFound = bean.getTypes().contains(CachingProvider.class);
-        }
-    }
-
-    public void addJCacheBeans(final @Observes AfterBeanDiscovery afterBeanDiscovery)
-    {
-        if (!ACTIVATED)
-        {
-            return;
-        }
-
-        if (cacheManagerFound && cacheProviderFound) {
-            return;
-        }
-
-        cachingProvider = Caching.getCachingProvider();
-        if (!cacheManagerFound)
-        {
-            cacheManager = cachingProvider.getCacheManager(
-                    cachingProvider.getDefaultURI(),
-                    cachingProvider.getDefaultClassLoader(),
-                    new Properties());
-            afterBeanDiscovery.addBean(new CacheManagerBean(cacheManager));
-        }
-        if (!cacheProviderFound)
-        {
-            afterBeanDiscovery.addBean(new CacheProviderBean(cachingProvider));
-        }
-    }
-
-    public void destroyIfCreated(final @Observes BeforeShutdown beforeShutdown)
-    {
-        if (cacheManager != null)
-        {
-            cacheManager.close();
-        }
-        if (cachingProvider != null)
-        {
-            cachingProvider.close();
-        }
-    }
-}
diff --git a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/closeable/Closeables.java b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/closeable/Closeables.java
deleted file mode 100644
index c8d4c96..0000000
--- a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/closeable/Closeables.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.extras.closeable;
-
-import java.io.Closeable;
-import java.io.IOException;
-
-public class Closeables
-{
-    public static void close(final Object... closeables) throws IOException
-    {
-        IOException e = null;
-        for (final Object closeable : closeables)
-        {
-            if (Closeable.class.isInstance(closeable))
-            {
-                try
-                {
-                    Closeable.class.cast(closeable).close();
-                } catch (final IOException ex) {
-                    if (e == null)
-                    {
-                        e = ex;
-                    }
-                }
-            }
-        }
-        if (e != null)
-        {
-            throw e;
-        }
-    }
-
-    private Closeables()
-    {
-        // no-op
-    }
-}
diff --git a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/loader/CacheLoaderAdapter.java b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/loader/CacheLoaderAdapter.java
deleted file mode 100644
index c30b0f1..0000000
--- a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/loader/CacheLoaderAdapter.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.extras.loader;
-
-import javax.cache.configuration.Factory;
-import javax.cache.integration.CacheLoader;
-import javax.cache.integration.CacheLoaderException;
-import java.util.HashMap;
-import java.util.Map;
-
-public abstract class CacheLoaderAdapter<K, V> implements CacheLoader<K, V>, Factory<CacheLoader<K, V>>
-{
-    @Override
-    public Map<K, V> loadAll(final Iterable<? extends K> keys) throws CacheLoaderException
-    {
-        final Map<K, V> result = new HashMap<K, V>();
-        for (final K k : keys)
-        {
-            final V v = load(k);
-            if (v != null)
-            {
-                result.put(k, v);
-            }
-        }
-        return result;
-    }
-
-    @Override
-    public CacheLoader<K, V> create()
-    {
-        return this;
-    }
-}
diff --git a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/loader/CompositeCacheLoader.java b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/loader/CompositeCacheLoader.java
deleted file mode 100644
index 560f5d1..0000000
--- a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/loader/CompositeCacheLoader.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.extras.loader;
-
-import org.apache.commons.jcs.jcache.extras.closeable.Closeables;
-
-import javax.cache.configuration.Factory;
-import javax.cache.integration.CacheLoader;
-import javax.cache.integration.CacheLoaderException;
-import java.io.Closeable;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-public class CompositeCacheLoader<K, V> implements CacheLoader<K, V>, Closeable, Factory<CacheLoader<K, V>>
-{
-    private final CacheLoader<K, V>[] delegates;
-
-    public CompositeCacheLoader(final CacheLoader<K, V>... delegates)
-    {
-        this.delegates = delegates;
-    }
-
-    @Override
-    public V load(final K key) throws CacheLoaderException
-    {
-        for (final CacheLoader<K, V> delegate : delegates)
-        {
-            final V v = delegate.load(key);
-            if (v != null)
-            {
-                return v;
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public Map<K, V> loadAll(final Iterable<? extends K> keys) throws CacheLoaderException
-    {
-        final Collection<K> list = new ArrayList<K>();
-        for (final K k : keys)
-        {
-            list.add(k);
-        }
-
-        final Map<K, V> result = new HashMap<K, V>();
-        for (final CacheLoader<K, V> delegate : delegates)
-        {
-            final Map<K, V> v = delegate.loadAll(list);
-            if (v != null)
-            {
-                result.putAll(v);
-                list.removeAll(v.keySet());
-                if (list.isEmpty())
-                {
-                    return v;
-                }
-            }
-        }
-
-        return result;
-    }
-
-    @Override
-    public void close() throws IOException
-    {
-        Closeables.close(delegates);
-    }
-
-    @Override
-    public CacheLoader<K, V> create()
-    {
-        return this;
-    }
-}
diff --git a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/web/InMemoryResponse.java b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/web/InMemoryResponse.java
deleted file mode 100644
index d381248..0000000
--- a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/web/InMemoryResponse.java
+++ /dev/null
@@ -1,276 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.extras.web;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.concurrent.CopyOnWriteArraySet;
-import javax.servlet.ServletOutputStream;
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpServletResponseWrapper;
-
-public class InMemoryResponse extends HttpServletResponseWrapper implements Serializable
-{
-    private final OutputStream buffer;
-
-    private final Collection<Cookie> cookies = new CopyOnWriteArraySet<Cookie>();
-    private final Map<String, List<Serializable>> headers = new TreeMap<String, List<Serializable>>(String.CASE_INSENSITIVE_ORDER);
-    private int status = SC_OK;
-    private String contentType = null;
-    private PrintWriter writer;
-    private int contentLength;
-
-    public InMemoryResponse(final HttpServletResponse response, final OutputStream baos)
-    {
-        super(response);
-        this.buffer = baos;
-    }
-
-    private List<Serializable> ensureHeaderExists(final String s)
-    {
-        List<Serializable> values = headers.get(s);
-        if (values == null) {
-            values = new LinkedList<Serializable>();
-            headers.put(s, values);
-        }
-        return values;
-    }
-
-    @Override
-    public void addCookie(final Cookie cookie)
-    {
-        super.addCookie(cookie);
-        cookies.add(cookie);
-    }
-
-    @Override
-    public void addDateHeader(final String s, final long l)
-    {
-        super.addDateHeader(s, l);
-        ensureHeaderExists(s).add(l);
-    }
-
-    @Override
-    public void addHeader(final String s, final String s2)
-    {
-        super.addHeader(s, s2);
-        ensureHeaderExists(s).add(s2);
-    }
-
-    @Override
-    public void addIntHeader(final String s, final int i)
-    {
-        super.addIntHeader(s, i);
-        ensureHeaderExists(s).add(i);
-    }
-
-    @Override
-    public boolean containsHeader(final String s)
-    {
-        return headers.containsKey(s);
-    }
-
-    @Override
-    public String getHeader(final String s)
-    {
-        final List<Serializable> serializables = headers.get(s);
-        if (serializables.isEmpty())
-        {
-            return null;
-        }
-        return serializables.iterator().next().toString();
-    }
-
-    @Override
-    public Collection<String> getHeaderNames()
-    {
-        return headers.keySet();
-    }
-
-    @Override
-    public Collection<String> getHeaders(final String s)
-    {
-        final List<Serializable> serializables = headers.get(s);
-        final Collection<String> strings = new ArrayList<String>(serializables.size());
-        for (final Serializable ser : serializables)
-        {
-            strings.add(ser.toString());
-        }
-        return strings;
-    }
-
-    @Override
-    public int getStatus()
-    {
-        return status;
-    }
-
-    @Override
-    public void sendError(final int i) throws IOException
-    {
-        status = i;
-        super.sendError(i);
-    }
-
-    @Override
-    public void sendError(final int i, final String s) throws IOException
-    {
-        status = i;
-        super.sendError(i, s);
-    }
-
-    @Override
-    public void sendRedirect(final String s) throws IOException
-    {
-        status = SC_MOVED_TEMPORARILY;
-        super.sendRedirect(s);
-    }
-
-    @Override
-    public void setDateHeader(final String s, final long l)
-    {
-        super.setDateHeader(s, l);
-        final List<Serializable> serializables = ensureHeaderExists(s);
-        serializables.clear();
-        serializables.add(l);
-    }
-
-    @Override
-    public void setHeader(final String s, final String s2)
-    {
-        super.setHeader(s, s2);
-        final List<Serializable> serializables = ensureHeaderExists(s);
-        serializables.clear();
-        serializables.add(s2);
-    }
-
-    @Override
-    public void setIntHeader(final String s, final int i)
-    {
-        super.setIntHeader(s, i);
-        final List<Serializable> serializables = ensureHeaderExists(s);
-        serializables.clear();
-        serializables.add(i);
-    }
-
-    @Override
-    public void setStatus(int i)
-    {
-        status = i;
-        super.setStatus(i);
-    }
-
-    @Override
-    public void setStatus(final int i, final String s)
-    {
-        status = i;
-        super.setStatus(i, s);
-    }
-
-    @Override
-    public String getContentType()
-    {
-        return contentType;
-    }
-
-    @Override
-    public ServletOutputStream getOutputStream() throws IOException
-    {
-        return new ServletOutputStream()
-        {
-            @Override
-            public void write(final int b) throws IOException
-            {
-                buffer.write(b);
-            }
-        };
-    }
-
-    @Override
-    public PrintWriter getWriter() throws IOException
-    {
-        if (writer == null) {
-            writer = new PrintWriter(new OutputStreamWriter(buffer, getCharacterEncoding()), true);
-        }
-        return writer;
-    }
-
-    @Override
-    public void reset()
-    {
-        super.reset();
-        status = SC_OK;
-        headers.clear();
-        cookies.clear();
-        contentType = null;
-        contentLength = 0;
-    }
-
-    @Override
-    public void setContentLength(final int i)
-    {
-        super.setContentLength(i);
-        contentLength = i;
-    }
-
-    @Override
-    public void setContentType(final String s)
-    {
-        contentType = s;
-        super.setContentType(s);
-    }
-
-    @Override
-    public void flushBuffer() throws IOException
-    {
-        if (writer != null)
-        {
-            writer.flush();
-        }
-        else
-        {
-            buffer.flush();
-        }
-    }
-
-    public int getContentLength()
-    {
-        return contentLength;
-    }
-
-    public Collection<Cookie> getCookies()
-    {
-        return cookies;
-    }
-
-    public Map<String, List<Serializable>> getHeaders()
-    {
-        return headers;
-    }
-}
diff --git a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/web/JCacheFilter.java b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/web/JCacheFilter.java
deleted file mode 100644
index cdfc644..0000000
--- a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/web/JCacheFilter.java
+++ /dev/null
@@ -1,341 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.extras.web;
-
-import javax.cache.Cache;
-import javax.cache.CacheManager;
-import javax.cache.Caching;
-import javax.cache.configuration.FactoryBuilder;
-import javax.cache.configuration.MutableConfiguration;
-import javax.cache.expiry.ExpiryPolicy;
-import javax.cache.integration.CacheLoader;
-import javax.cache.integration.CacheWriter;
-import javax.cache.spi.CachingProvider;
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.BufferedOutputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.Serializable;
-import java.net.URI;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-import java.util.zip.GZIPOutputStream;
-
-import static java.util.Collections.list;
-import static javax.servlet.http.HttpServletResponse.SC_OK;
-
-public class JCacheFilter implements Filter
-{
-    private Cache<PageKey, Page> cache;
-    private CachingProvider provider;
-    private CacheManager manager;
-
-    @Override
-    public void init(final FilterConfig filterConfig) throws ServletException
-    {
-        final ClassLoader classLoader = filterConfig.getServletContext().getClassLoader();
-        provider = Caching.getCachingProvider(classLoader);
-
-        String uri = filterConfig.getInitParameter("configuration");
-        if (uri == null)
-        {
-            uri = provider.getDefaultURI().toString();
-        }
-        final Properties properties = new Properties();
-        for (final String key : list(filterConfig.getInitParameterNames()))
-        {
-            final String value = filterConfig.getInitParameter(key);
-            if (value != null)
-            {
-                properties.put(key, value);
-            }
-        }
-        manager = provider.getCacheManager(URI.create(uri), classLoader, properties);
-
-        String cacheName = filterConfig.getInitParameter("cache-name");
-        if (cacheName == null)
-        {
-            cacheName = JCacheFilter.class.getName();
-        }
-        cache = manager.getCache(cacheName);
-        if (cache == null)
-        {
-            final MutableConfiguration<PageKey, Page> configuration = new MutableConfiguration<PageKey, Page>()
-                    .setStoreByValue(false);
-            configuration.setReadThrough("true".equals(properties.getProperty("read-through", "false")));
-            configuration.setWriteThrough("true".equals(properties.getProperty("write-through", "false")));
-            if (configuration.isReadThrough())
-            {
-                configuration.setCacheLoaderFactory(new FactoryBuilder.ClassFactory<CacheLoader<PageKey, Page>>(properties.getProperty("cache-loader-factory")));
-            }
-            if (configuration.isWriteThrough())
-            {
-                configuration.setCacheWriterFactory(new FactoryBuilder.ClassFactory<CacheWriter<? super PageKey, ? super Page>>(properties.getProperty("cache-writer-factory")));
-            }
-            final String expirtyPolicy = properties.getProperty("expiry-policy-factory");
-            if (expirtyPolicy != null)
-            {
-                configuration.setExpiryPolicyFactory(new FactoryBuilder.ClassFactory<ExpiryPolicy>(expirtyPolicy));
-            }
-            configuration.setManagementEnabled("true".equals(properties.getProperty("management-enabled", "false")));
-            configuration.setStatisticsEnabled("true".equals(properties.getProperty("statistics-enabled", "false")));
-            cache = manager.createCache(cacheName, configuration);
-        }
-    }
-
-    @Override
-    public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException
-    {
-        boolean gzip = false;
-        if (HttpServletRequest.class.isInstance(servletRequest))
-        {
-            final Enumeration<String> acceptEncoding = HttpServletRequest.class.cast(servletRequest).getHeaders("Accept-Encoding");
-            while (acceptEncoding != null && acceptEncoding.hasMoreElements())
-            {
-                if ("gzip".equals(acceptEncoding.nextElement()))
-                {
-                    gzip = true;
-                    break;
-                }
-            }
-        }
-
-        final HttpServletResponse httpServletResponse = HttpServletResponse.class.cast(servletResponse);
-        checkResponse(httpServletResponse);
-
-        final PageKey key = new PageKey(key(servletRequest), gzip);
-        Page page = cache.get(key);
-        if (page == null)
-        {
-            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
-            final InMemoryResponse response;
-            if (gzip)
-            {
-                response = new InMemoryResponse(httpServletResponse, new GZIPOutputStream(baos));
-            }
-            else
-            {
-                response = new InMemoryResponse(httpServletResponse, baos);
-            }
-            filterChain.doFilter(servletRequest, response);
-            response.flushBuffer();
-
-            page = new Page(
-                    response.getStatus(),
-                    response.getContentType(),
-                    response.getContentLength(),
-                    response.getCookies(),
-                    response.getHeaders(),
-                    baos.toByteArray());
-            cache.put(key, page);
-        }
-
-        if (page.status == SC_OK) {
-            checkResponse(httpServletResponse);
-
-            if (gzip)
-            {
-                httpServletResponse.setHeader("Content-Encoding", "gzip");
-            }
-
-            httpServletResponse.setStatus(page.status);
-            if (page.contentType != null)
-            {
-                httpServletResponse.setContentType(page.contentType);
-            }
-            if (page.contentLength > 0)
-            {
-                httpServletResponse.setContentLength(page.contentLength);
-            }
-            for (final Cookie c : page.cookies)
-            {
-                httpServletResponse.addCookie(c);
-            }
-            for (final Map.Entry<String, List<Serializable>> entry : page.headers.entrySet())
-            {
-                for (final Serializable value : entry.getValue())
-                {
-                    if (Integer.class.isInstance(value))
-                    {
-                        httpServletResponse.addIntHeader(entry.getKey(), Integer.class.cast(value));
-                    }
-                    else if (String.class.isInstance(value))
-                    {
-                        httpServletResponse.addHeader(entry.getKey(), String.class.cast(value));
-                    }
-                    else if (Long.class.isInstance(value))
-                    {
-                        httpServletResponse.addDateHeader(entry.getKey(), Long.class.cast(value));
-                    }
-                }
-            }
-            httpServletResponse.setContentLength(page.out.length);
-            final BufferedOutputStream bos = new BufferedOutputStream(httpServletResponse.getOutputStream());
-            if (page.out.length != 0)
-            {
-                bos.write(page.out);
-            }
-            else
-            {
-                bos.write(new byte[0]);
-            }
-            bos.flush();
-        }
-    }
-
-    protected String key(final ServletRequest servletRequest)
-    {
-        if (HttpServletRequest.class.isInstance(servletRequest))
-        {
-            final HttpServletRequest request = HttpServletRequest.class.cast(servletRequest);
-            return request.getMethod() + '_' + request.getRequestURI() + '_' + request.getQueryString();
-        }
-        return servletRequest.toString();
-    }
-
-    private void checkResponse(final ServletResponse servletResponse)
-    {
-        if (servletResponse.isCommitted()) {
-            throw new IllegalStateException("Response committed");
-        }
-    }
-
-    @Override
-    public void destroy()
-    {
-        if (!cache.isClosed())
-        {
-            cache.close();
-        }
-        if (!manager.isClosed())
-        {
-            manager.close();
-        }
-        provider.close();
-    }
-
-    protected static class PageKey implements Serializable {
-        private final String uri;
-        private boolean gzip;
-
-        public PageKey(final String uri, final boolean gzip)
-        {
-            this.uri = uri;
-            this.gzip = gzip;
-        }
-
-        public void setGzip(final boolean gzip)
-        {
-            this.gzip = gzip;
-        }
-
-        @Override
-        public boolean equals(final Object o)
-        {
-            if (this == o)
-            {
-                return true;
-            }
-            if (o == null || getClass() != o.getClass())
-            {
-                return false;
-            }
-
-            final PageKey pageKey = PageKey.class.cast(o);
-            return gzip == pageKey.gzip && uri.equals(pageKey.uri);
-
-        }
-
-        @Override
-        public int hashCode()
-        {
-            int result = uri.hashCode();
-            result = 31 * result + (gzip ? 1 : 0);
-            return result;
-        }
-    }
-
-    protected static class Page implements Serializable {
-        private final int status;
-        private final String contentType;
-        private final int contentLength;
-        private final Collection<Cookie> cookies;
-        private final Map<String, List<Serializable>> headers;
-        private final byte[] out;
-
-        public Page(final int status,
-                    final String contentType, final int contentLength,
-                    final Collection<Cookie> cookies, final Map<String, List<Serializable>> headers,
-                    final byte[] out)
-        {
-            this.status = status;
-            this.contentType = contentType;
-            this.contentLength = contentLength;
-            this.cookies = cookies;
-            this.headers = headers;
-            this.out = out;
-        }
-
-        @Override
-        public boolean equals(final Object o)
-        {
-            if (this == o)
-            {
-                return true;
-            }
-            if (o == null || getClass() != o.getClass())
-            {
-                return false;
-            }
-
-            final Page page = Page.class.cast(o);
-            return contentLength == page.contentLength
-                    && status == page.status
-                    && !(contentType != null ? !contentType.equals(page.contentType) : page.contentType != null)
-                    && cookies.equals(page.cookies)
-                    && headers.equals(page.headers)
-                    && Arrays.equals(out, page.out);
-
-        }
-
-        @Override
-        public int hashCode()
-        {
-            int result = status;
-            result = 31 * result + (contentType != null ? contentType.hashCode() : 0);
-            result = 31 * result + contentLength;
-            result = 31 * result + cookies.hashCode();
-            result = 31 * result + headers.hashCode();
-            result = 31 * result + Arrays.hashCode(out);
-            return result;
-        }
-    }
-}
diff --git a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/writer/AsyncCacheWriter.java b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/writer/AsyncCacheWriter.java
deleted file mode 100644
index 53d2530..0000000
--- a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/writer/AsyncCacheWriter.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.extras.writer;
-
-import javax.cache.Cache;
-import javax.cache.configuration.Factory;
-import javax.cache.integration.CacheWriter;
-import javax.cache.integration.CacheWriterException;
-import java.io.Closeable;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.List;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-public class AsyncCacheWriter<K, V> implements CacheWriter<K, V>, Closeable, Factory<CacheWriter<K, V>>
-{
-    private static final Logger LOGGER = Logger.getLogger(AsyncCacheWriter.class.getName());
-
-    private final CacheWriter<K, V> writer;
-    private final ExecutorService pool;
-
-    public AsyncCacheWriter(final CacheWriter<K, V> delegate, final int poolSize)
-    {
-        writer = delegate;
-        pool = Executors.newFixedThreadPool(
-                poolSize, new DaemonThreadFactory(delegate.getClass().getName() + "-" + delegate.hashCode() + "-"));
-    }
-
-    @Override
-    public void write(final Cache.Entry<? extends K, ? extends V> entry) throws CacheWriterException
-    {
-        pool.submit(new ExceptionProtectionRunnable()
-        {
-            @Override
-            public void doRun()
-            {
-                writer.write(entry);
-            }
-        });
-    }
-
-    @Override
-    public void writeAll(final Collection<Cache.Entry<? extends K, ? extends V>> entries) throws CacheWriterException
-    {
-        pool.submit(new ExceptionProtectionRunnable()
-        {
-            @Override
-            public void doRun()
-            {
-                writer.writeAll(entries);
-            }
-        });
-    }
-
-    @Override
-    public void delete(final Object key) throws CacheWriterException
-    {
-        pool.submit(new ExceptionProtectionRunnable()
-        {
-            @Override
-            public void doRun()
-            {
-                writer.delete(key);
-            }
-        });
-    }
-
-    @Override
-    public void deleteAll(final Collection<?> keys) throws CacheWriterException
-    {
-        pool.submit(new ExceptionProtectionRunnable()
-        {
-            @Override
-            public void doRun()
-            {
-                writer.deleteAll(keys);
-            }
-        });
-    }
-
-    @Override
-    public void close() throws IOException
-    {
-        final List<Runnable> runnables = pool.shutdownNow();
-        for (final Runnable r : runnables)
-        {
-            r.run();
-        }
-    }
-
-    @Override
-    public CacheWriter<K, V> create()
-    {
-        return this;
-    }
-
-    // avoid dep on impl
-    private static class DaemonThreadFactory implements ThreadFactory
-    {
-        private final AtomicInteger index = new AtomicInteger(1);
-        private final String prefix;
-
-        public DaemonThreadFactory(final String prefix)
-        {
-            this.prefix = prefix;
-        }
-
-        @Override
-        public Thread newThread( final Runnable runner )
-        {
-            final Thread t = new Thread( runner );
-            t.setName(prefix + index.getAndIncrement());
-            t.setDaemon(true);
-            return t;
-        }
-    }
-
-    private static abstract class ExceptionProtectionRunnable implements Runnable
-    {
-        @Override
-        public void run()
-        {
-            try
-            {
-                doRun();
-            }
-            catch (final Exception e)
-            {
-                LOGGER.log(Level.SEVERE, e.getMessage(), e);
-            }
-        }
-
-        protected abstract void doRun();
-    }
-}
diff --git a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/writer/CacheWriterAdapter.java b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/writer/CacheWriterAdapter.java
deleted file mode 100644
index 79b59b9..0000000
--- a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/writer/CacheWriterAdapter.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.extras.writer;
-
-import javax.cache.Cache;
-import javax.cache.configuration.Factory;
-import javax.cache.integration.CacheWriter;
-import javax.cache.integration.CacheWriterException;
-import java.util.Collection;
-
-public abstract class CacheWriterAdapter<K, V> implements CacheWriter<K, V>, Factory<CacheWriter<K, V>>
-{
-    @Override
-    public void writeAll(final Collection<Cache.Entry<? extends K, ? extends V>> entries) throws CacheWriterException
-    {
-        for (final Cache.Entry<? extends K, ? extends V> entry : entries)
-        {
-            write(entry);
-        }
-    }
-
-    @Override
-    public void deleteAll(final Collection<?> keys) throws CacheWriterException
-    {
-        for (final Object k : keys)
-        {
-            delete(k);
-        }
-    }
-
-    @Override
-    public CacheWriter<K, V> create()
-    {
-        return this;
-    }
-}
diff --git a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/writer/CompositeCacheWriter.java b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/writer/CompositeCacheWriter.java
deleted file mode 100644
index 970ab13..0000000
--- a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs/jcache/extras/writer/CompositeCacheWriter.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.extras.writer;
-
-import org.apache.commons.jcs.jcache.extras.closeable.Closeables;
-
-import javax.cache.Cache;
-import javax.cache.configuration.Factory;
-import javax.cache.integration.CacheWriter;
-import javax.cache.integration.CacheWriterException;
-import java.io.Closeable;
-import java.io.IOException;
-import java.util.Collection;
-
-public class CompositeCacheWriter<K, V> implements CacheWriter<K, V>, Closeable, Factory<CacheWriter<K, V>>
-{
-    private final CacheWriter<K, V>[] writers;
-
-    public CompositeCacheWriter(final CacheWriter<K, V>... writers)
-    {
-        this.writers = writers;
-    }
-
-    @Override
-    public void write(final Cache.Entry<? extends K, ? extends V> entry) throws CacheWriterException
-    {
-        CacheWriterException e = null;
-        for (final CacheWriter<K, V> writer : writers)
-        {
-            try
-            {
-                writer.write(entry);
-            }
-            catch (final CacheWriterException ex)
-            {
-                if (e == null)
-                {
-                    e = ex;
-                }
-            }
-        }
-        if (e != null)
-        {
-            throw e;
-        }
-    }
-
-    @Override
-    public void writeAll(final Collection<Cache.Entry<? extends K, ? extends V>> entries) throws CacheWriterException
-    {
-        CacheWriterException e = null;
-        for (final CacheWriter<K, V> writer : writers)
-        {
-            try
-            {
-                writer.writeAll(entries);
-            }
-            catch (final CacheWriterException ex)
-            {
-                if (e == null)
-                {
-                    e = ex;
-                }
-            }
-        }
-        if (e != null)
-        {
-            throw e;
-        }
-    }
-
-    @Override
-    public void delete(final Object key) throws CacheWriterException
-    {
-        CacheWriterException e = null;
-        for (final CacheWriter<K, V> writer : writers)
-        {
-            try
-            {
-                writer.delete(key);
-            }
-            catch (final CacheWriterException ex)
-            {
-                if (e == null)
-                {
-                    e = ex;
-                }
-            }
-        }
-        if (e != null)
-        {
-            throw e;
-        }
-    }
-
-    @Override
-    public void deleteAll(final Collection<?> keys) throws CacheWriterException
-    {
-        CacheWriterException e = null;
-        for (final CacheWriter<K, V> writer : writers)
-        {
-            try
-            {
-                writer.deleteAll(keys);
-            }
-            catch (final CacheWriterException ex)
-            {
-                if (e == null)
-                {
-                    e = ex;
-                }
-            }
-        }
-        if (e != null)
-        {
-            throw e;
-        }
-    }
-
-    @Override
-    public CacheWriter<K, V> create()
-    {
-        return this;
-    }
-
-    @Override
-    public void close() throws IOException
-    {
-        Closeables.close(writers);
-    }
-}
diff --git a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/cdi/AnyLiteral.java b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/cdi/AnyLiteral.java
new file mode 100644
index 0000000..2371cdf
--- /dev/null
+++ b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/cdi/AnyLiteral.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.extras.cdi;
+
+import javax.enterprise.inject.Any;
+import javax.enterprise.util.AnnotationLiteral;
+
+public class AnyLiteral extends AnnotationLiteral<Any> implements Any
+{
+    public static final AnyLiteral INSTANCE = new AnyLiteral();
+
+    @Override
+    public String toString()
+    {
+        return "@javax.enterprise.inject.Any()";
+    }
+}
diff --git a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/cdi/CacheManagerBean.java b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/cdi/CacheManagerBean.java
new file mode 100644
index 0000000..18b600f
--- /dev/null
+++ b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/cdi/CacheManagerBean.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.extras.cdi;
+
+import javax.cache.CacheManager;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.InjectionPoint;
+import javax.enterprise.inject.spi.PassivationCapable;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.HashSet;
+import java.util.Set;
+
+import static java.util.Collections.emptySet;
+
+public class CacheManagerBean implements Bean<CacheManager>, PassivationCapable
+{
+    private final Set<Type> types;
+    private final Set<Annotation> qualifiers;
+    private final CacheManager manager;
+    private final String id;
+
+    public CacheManagerBean(final CacheManager cacheManager)
+    {
+        manager = cacheManager;
+        id = getClass().getName() + "-" + hashCode();
+
+        types = new HashSet<Type>();
+        types.add(CacheManager.class);
+        types.add(Object.class);
+
+        qualifiers = new HashSet<Annotation>();
+        qualifiers.add(DefaultLiteral.INSTANCE);
+        qualifiers.add(AnyLiteral.INSTANCE);
+    }
+
+    @Override
+    public Set<Type> getTypes()
+    {
+        return types;
+    }
+
+    @Override
+    public Set<Annotation> getQualifiers()
+    {
+        return qualifiers;
+    }
+
+    @Override
+    public Class<? extends Annotation> getScope()
+    {
+        return ApplicationScoped.class;
+    }
+
+    @Override
+    public String getName()
+    {
+        return null;
+    }
+
+    @Override
+    public boolean isNullable()
+    {
+        return false;
+    }
+
+    @Override
+    public Set<InjectionPoint> getInjectionPoints()
+    {
+        return emptySet();
+    }
+
+    @Override
+    public Class<?> getBeanClass()
+    {
+        return CacheManager.class;
+    }
+
+    @Override
+    public Set<Class<? extends Annotation>> getStereotypes()
+    {
+        return emptySet();
+    }
+
+    @Override
+    public boolean isAlternative()
+    {
+        return false;
+    }
+
+    @Override
+    public CacheManager create(CreationalContext<CacheManager> cacheManagerCreationalContext)
+    {
+        return manager;
+    }
+
+    @Override
+    public void destroy(CacheManager cacheManager, CreationalContext<CacheManager> cacheManagerCreationalContext)
+    {
+        manager.close();
+    }
+
+    @Override
+    public String getId()
+    {
+        return id;
+    }
+}
diff --git a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/cdi/CacheProviderBean.java b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/cdi/CacheProviderBean.java
new file mode 100644
index 0000000..e9bd965
--- /dev/null
+++ b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/cdi/CacheProviderBean.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.extras.cdi;
+
+import javax.cache.spi.CachingProvider;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.InjectionPoint;
+import javax.enterprise.inject.spi.PassivationCapable;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.HashSet;
+import java.util.Set;
+
+import static java.util.Collections.emptySet;
+
+public class CacheProviderBean implements Bean<CachingProvider>, PassivationCapable
+{
+    private final Set<Type> types;
+    private final Set<Annotation> qualifiers;
+    private final CachingProvider provider;
+    private final String id;
+
+    public CacheProviderBean(final CachingProvider cacheManager)
+    {
+        provider = cacheManager;
+        id = getClass().getName() + "-" + hashCode();
+
+        types = new HashSet<Type>();
+        types.add(CachingProvider.class);
+        types.add(Object.class);
+
+        qualifiers = new HashSet<Annotation>();
+        qualifiers.add(DefaultLiteral.INSTANCE);
+        qualifiers.add(AnyLiteral.INSTANCE);
+    }
+
+    @Override
+    public Set<Type> getTypes()
+    {
+        return types;
+    }
+
+    @Override
+    public Set<Annotation> getQualifiers()
+    {
+        return qualifiers;
+    }
+
+    @Override
+    public Class<? extends Annotation> getScope()
+    {
+        return ApplicationScoped.class;
+    }
+
+    @Override
+    public String getName()
+    {
+        return null;
+    }
+
+    @Override
+    public boolean isNullable()
+    {
+        return false;
+    }
+
+    @Override
+    public Set<InjectionPoint> getInjectionPoints()
+    {
+        return emptySet();
+    }
+
+    @Override
+    public Class<?> getBeanClass()
+    {
+        return CachingProvider.class;
+    }
+
+    @Override
+    public Set<Class<? extends Annotation>> getStereotypes()
+    {
+        return emptySet();
+    }
+
+    @Override
+    public boolean isAlternative()
+    {
+        return false;
+    }
+
+    @Override
+    public CachingProvider create(final CreationalContext<CachingProvider> cacheManagerCreationalContext)
+    {
+        return provider;
+    }
+
+    @Override
+    public void destroy(final CachingProvider cacheProvider, final CreationalContext<CachingProvider> cacheManagerCreationalContext)
+    {
+        provider.close();
+    }
+
+    @Override
+    public String getId()
+    {
+        return id;
+    }
+}
diff --git a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/cdi/DefaultLiteral.java b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/cdi/DefaultLiteral.java
new file mode 100644
index 0000000..fbf7d5d
--- /dev/null
+++ b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/cdi/DefaultLiteral.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.extras.cdi;
+
+import javax.enterprise.inject.Default;
+import javax.enterprise.util.AnnotationLiteral;
+
+public class DefaultLiteral extends AnnotationLiteral<Default> implements Default
+{
+    public static final DefaultLiteral INSTANCE = new DefaultLiteral();
+
+    @Override
+    public String toString()
+    {
+        return "@javax.enterprise.inject.Default()";
+    }
+}
diff --git a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/cdi/ExtraJCacheExtension.java b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/cdi/ExtraJCacheExtension.java
new file mode 100644
index 0000000..75a7c3d
--- /dev/null
+++ b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/cdi/ExtraJCacheExtension.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.extras.cdi;
+
+import javax.cache.CacheManager;
+import javax.cache.Caching;
+import javax.cache.spi.CachingProvider;
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.spi.AfterBeanDiscovery;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeforeShutdown;
+import javax.enterprise.inject.spi.Extension;
+import javax.enterprise.inject.spi.ProcessBean;
+import java.util.Properties;
+
+// add default CacheProvider and CacheManager
+public class ExtraJCacheExtension implements Extension
+{
+    private static final boolean ACTIVATED = "true".equals(System.getProperty("org.apache.jcs.extra.cdi", "true"));
+
+    private boolean cacheManagerFound = false;
+    private boolean cacheProviderFound = false;
+    private CacheManager cacheManager;
+    private CachingProvider cachingProvider;
+
+    public <A> void processBean(final @Observes ProcessBean<A> processBeanEvent)
+    {
+        if (!ACTIVATED)
+        {
+            return;
+        }
+
+        if (cacheManagerFound && cacheProviderFound)
+        {
+            return;
+        }
+
+        final Bean<A> bean = processBeanEvent.getBean();
+        if (CacheManagerBean.class.isInstance(bean) || CacheProviderBean.class.isInstance(bean))
+        {
+            return;
+        }
+
+        if (!cacheManagerFound)
+        {
+            cacheManagerFound = bean.getTypes().contains(CacheManager.class);
+        }
+        if (!cacheProviderFound)
+        {
+            cacheProviderFound = bean.getTypes().contains(CachingProvider.class);
+        }
+    }
+
+    public void addJCacheBeans(final @Observes AfterBeanDiscovery afterBeanDiscovery)
+    {
+        if (!ACTIVATED)
+        {
+            return;
+        }
+
+        if (cacheManagerFound && cacheProviderFound) {
+            return;
+        }
+
+        cachingProvider = Caching.getCachingProvider();
+        if (!cacheManagerFound)
+        {
+            cacheManager = cachingProvider.getCacheManager(
+                    cachingProvider.getDefaultURI(),
+                    cachingProvider.getDefaultClassLoader(),
+                    new Properties());
+            afterBeanDiscovery.addBean(new CacheManagerBean(cacheManager));
+        }
+        if (!cacheProviderFound)
+        {
+            afterBeanDiscovery.addBean(new CacheProviderBean(cachingProvider));
+        }
+    }
+
+    public void destroyIfCreated(final @Observes BeforeShutdown beforeShutdown)
+    {
+        if (cacheManager != null)
+        {
+            cacheManager.close();
+        }
+        if (cachingProvider != null)
+        {
+            cachingProvider.close();
+        }
+    }
+}
diff --git a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/closeable/Closeables.java b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/closeable/Closeables.java
new file mode 100644
index 0000000..27b076e
--- /dev/null
+++ b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/closeable/Closeables.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.extras.closeable;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+public class Closeables
+{
+    public static void close(final Object... closeables) throws IOException
+    {
+        IOException e = null;
+        for (final Object closeable : closeables)
+        {
+            if (Closeable.class.isInstance(closeable))
+            {
+                try
+                {
+                    Closeable.class.cast(closeable).close();
+                } catch (final IOException ex) {
+                    if (e == null)
+                    {
+                        e = ex;
+                    }
+                }
+            }
+        }
+        if (e != null)
+        {
+            throw e;
+        }
+    }
+
+    private Closeables()
+    {
+        // no-op
+    }
+}
diff --git a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/loader/CacheLoaderAdapter.java b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/loader/CacheLoaderAdapter.java
new file mode 100644
index 0000000..7e77c4a
--- /dev/null
+++ b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/loader/CacheLoaderAdapter.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.extras.loader;
+
+import javax.cache.configuration.Factory;
+import javax.cache.integration.CacheLoader;
+import javax.cache.integration.CacheLoaderException;
+import java.util.HashMap;
+import java.util.Map;
+
+public abstract class CacheLoaderAdapter<K, V> implements CacheLoader<K, V>, Factory<CacheLoader<K, V>>
+{
+    @Override
+    public Map<K, V> loadAll(final Iterable<? extends K> keys) throws CacheLoaderException
+    {
+        final Map<K, V> result = new HashMap<K, V>();
+        for (final K k : keys)
+        {
+            final V v = load(k);
+            if (v != null)
+            {
+                result.put(k, v);
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public CacheLoader<K, V> create()
+    {
+        return this;
+    }
+}
diff --git a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/loader/CompositeCacheLoader.java b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/loader/CompositeCacheLoader.java
new file mode 100644
index 0000000..767e177
--- /dev/null
+++ b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/loader/CompositeCacheLoader.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.extras.loader;
+
+import javax.cache.configuration.Factory;
+import javax.cache.integration.CacheLoader;
+import javax.cache.integration.CacheLoaderException;
+
+import org.apache.commons.jcs3.jcache.extras.closeable.Closeables;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+public class CompositeCacheLoader<K, V> implements CacheLoader<K, V>, Closeable, Factory<CacheLoader<K, V>>
+{
+    private final CacheLoader<K, V>[] delegates;
+
+    public CompositeCacheLoader(final CacheLoader<K, V>... delegates)
+    {
+        this.delegates = delegates;
+    }
+
+    @Override
+    public V load(final K key) throws CacheLoaderException
+    {
+        for (final CacheLoader<K, V> delegate : delegates)
+        {
+            final V v = delegate.load(key);
+            if (v != null)
+            {
+                return v;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Map<K, V> loadAll(final Iterable<? extends K> keys) throws CacheLoaderException
+    {
+        final Collection<K> list = new ArrayList<K>();
+        for (final K k : keys)
+        {
+            list.add(k);
+        }
+
+        final Map<K, V> result = new HashMap<K, V>();
+        for (final CacheLoader<K, V> delegate : delegates)
+        {
+            final Map<K, V> v = delegate.loadAll(list);
+            if (v != null)
+            {
+                result.putAll(v);
+                list.removeAll(v.keySet());
+                if (list.isEmpty())
+                {
+                    return v;
+                }
+            }
+        }
+
+        return result;
+    }
+
+    @Override
+    public void close() throws IOException
+    {
+        Closeables.close(delegates);
+    }
+
+    @Override
+    public CacheLoader<K, V> create()
+    {
+        return this;
+    }
+}
diff --git a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/web/InMemoryResponse.java b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/web/InMemoryResponse.java
new file mode 100644
index 0000000..0f468d7
--- /dev/null
+++ b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/web/InMemoryResponse.java
@@ -0,0 +1,276 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.extras.web;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+public class InMemoryResponse extends HttpServletResponseWrapper implements Serializable
+{
+    private final OutputStream buffer;
+
+    private final Collection<Cookie> cookies = new CopyOnWriteArraySet<Cookie>();
+    private final Map<String, List<Serializable>> headers = new TreeMap<String, List<Serializable>>(String.CASE_INSENSITIVE_ORDER);
+    private int status = SC_OK;
+    private String contentType = null;
+    private PrintWriter writer;
+    private int contentLength;
+
+    public InMemoryResponse(final HttpServletResponse response, final OutputStream baos)
+    {
+        super(response);
+        this.buffer = baos;
+    }
+
+    private List<Serializable> ensureHeaderExists(final String s)
+    {
+        List<Serializable> values = headers.get(s);
+        if (values == null) {
+            values = new LinkedList<Serializable>();
+            headers.put(s, values);
+        }
+        return values;
+    }
+
+    @Override
+    public void addCookie(final Cookie cookie)
+    {
+        super.addCookie(cookie);
+        cookies.add(cookie);
+    }
+
+    @Override
+    public void addDateHeader(final String s, final long l)
+    {
+        super.addDateHeader(s, l);
+        ensureHeaderExists(s).add(l);
+    }
+
+    @Override
+    public void addHeader(final String s, final String s2)
+    {
+        super.addHeader(s, s2);
+        ensureHeaderExists(s).add(s2);
+    }
+
+    @Override
+    public void addIntHeader(final String s, final int i)
+    {
+        super.addIntHeader(s, i);
+        ensureHeaderExists(s).add(i);
+    }
+
+    @Override
+    public boolean containsHeader(final String s)
+    {
+        return headers.containsKey(s);
+    }
+
+    @Override
+    public String getHeader(final String s)
+    {
+        final List<Serializable> serializables = headers.get(s);
+        if (serializables.isEmpty())
+        {
+            return null;
+        }
+        return serializables.iterator().next().toString();
+    }
+
+    @Override
+    public Collection<String> getHeaderNames()
+    {
+        return headers.keySet();
+    }
+
+    @Override
+    public Collection<String> getHeaders(final String s)
+    {
+        final List<Serializable> serializables = headers.get(s);
+        final Collection<String> strings = new ArrayList<String>(serializables.size());
+        for (final Serializable ser : serializables)
+        {
+            strings.add(ser.toString());
+        }
+        return strings;
+    }
+
+    @Override
+    public int getStatus()
+    {
+        return status;
+    }
+
+    @Override
+    public void sendError(final int i) throws IOException
+    {
+        status = i;
+        super.sendError(i);
+    }
+
+    @Override
+    public void sendError(final int i, final String s) throws IOException
+    {
+        status = i;
+        super.sendError(i, s);
+    }
+
+    @Override
+    public void sendRedirect(final String s) throws IOException
+    {
+        status = SC_MOVED_TEMPORARILY;
+        super.sendRedirect(s);
+    }
+
+    @Override
+    public void setDateHeader(final String s, final long l)
+    {
+        super.setDateHeader(s, l);
+        final List<Serializable> serializables = ensureHeaderExists(s);
+        serializables.clear();
+        serializables.add(l);
+    }
+
+    @Override
+    public void setHeader(final String s, final String s2)
+    {
+        super.setHeader(s, s2);
+        final List<Serializable> serializables = ensureHeaderExists(s);
+        serializables.clear();
+        serializables.add(s2);
+    }
+
+    @Override
+    public void setIntHeader(final String s, final int i)
+    {
+        super.setIntHeader(s, i);
+        final List<Serializable> serializables = ensureHeaderExists(s);
+        serializables.clear();
+        serializables.add(i);
+    }
+
+    @Override
+    public void setStatus(int i)
+    {
+        status = i;
+        super.setStatus(i);
+    }
+
+    @Override
+    public void setStatus(final int i, final String s)
+    {
+        status = i;
+        super.setStatus(i, s);
+    }
+
+    @Override
+    public String getContentType()
+    {
+        return contentType;
+    }
+
+    @Override
+    public ServletOutputStream getOutputStream() throws IOException
+    {
+        return new ServletOutputStream()
+        {
+            @Override
+            public void write(final int b) throws IOException
+            {
+                buffer.write(b);
+            }
+        };
+    }
+
+    @Override
+    public PrintWriter getWriter() throws IOException
+    {
+        if (writer == null) {
+            writer = new PrintWriter(new OutputStreamWriter(buffer, getCharacterEncoding()), true);
+        }
+        return writer;
+    }
+
+    @Override
+    public void reset()
+    {
+        super.reset();
+        status = SC_OK;
+        headers.clear();
+        cookies.clear();
+        contentType = null;
+        contentLength = 0;
+    }
+
+    @Override
+    public void setContentLength(final int i)
+    {
+        super.setContentLength(i);
+        contentLength = i;
+    }
+
+    @Override
+    public void setContentType(final String s)
+    {
+        contentType = s;
+        super.setContentType(s);
+    }
+
+    @Override
+    public void flushBuffer() throws IOException
+    {
+        if (writer != null)
+        {
+            writer.flush();
+        }
+        else
+        {
+            buffer.flush();
+        }
+    }
+
+    public int getContentLength()
+    {
+        return contentLength;
+    }
+
+    public Collection<Cookie> getCookies()
+    {
+        return cookies;
+    }
+
+    public Map<String, List<Serializable>> getHeaders()
+    {
+        return headers;
+    }
+}
diff --git a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/web/JCacheFilter.java b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/web/JCacheFilter.java
new file mode 100644
index 0000000..ce8782d
--- /dev/null
+++ b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/web/JCacheFilter.java
@@ -0,0 +1,341 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.extras.web;
+
+import javax.cache.Cache;
+import javax.cache.CacheManager;
+import javax.cache.Caching;
+import javax.cache.configuration.FactoryBuilder;
+import javax.cache.configuration.MutableConfiguration;
+import javax.cache.expiry.ExpiryPolicy;
+import javax.cache.integration.CacheLoader;
+import javax.cache.integration.CacheWriter;
+import javax.cache.spi.CachingProvider;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.Serializable;
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.zip.GZIPOutputStream;
+
+import static java.util.Collections.list;
+import static javax.servlet.http.HttpServletResponse.SC_OK;
+
+public class JCacheFilter implements Filter
+{
+    private Cache<PageKey, Page> cache;
+    private CachingProvider provider;
+    private CacheManager manager;
+
+    @Override
+    public void init(final FilterConfig filterConfig) throws ServletException
+    {
+        final ClassLoader classLoader = filterConfig.getServletContext().getClassLoader();
+        provider = Caching.getCachingProvider(classLoader);
+
+        String uri = filterConfig.getInitParameter("configuration");
+        if (uri == null)
+        {
+            uri = provider.getDefaultURI().toString();
+        }
+        final Properties properties = new Properties();
+        for (final String key : list(filterConfig.getInitParameterNames()))
+        {
+            final String value = filterConfig.getInitParameter(key);
+            if (value != null)
+            {
+                properties.put(key, value);
+            }
+        }
+        manager = provider.getCacheManager(URI.create(uri), classLoader, properties);
+
+        String cacheName = filterConfig.getInitParameter("cache-name");
+        if (cacheName == null)
+        {
+            cacheName = JCacheFilter.class.getName();
+        }
+        cache = manager.getCache(cacheName);
+        if (cache == null)
+        {
+            final MutableConfiguration<PageKey, Page> configuration = new MutableConfiguration<PageKey, Page>()
+                    .setStoreByValue(false);
+            configuration.setReadThrough("true".equals(properties.getProperty("read-through", "false")));
+            configuration.setWriteThrough("true".equals(properties.getProperty("write-through", "false")));
+            if (configuration.isReadThrough())
+            {
+                configuration.setCacheLoaderFactory(new FactoryBuilder.ClassFactory<CacheLoader<PageKey, Page>>(properties.getProperty("cache-loader-factory")));
+            }
+            if (configuration.isWriteThrough())
+            {
+                configuration.setCacheWriterFactory(new FactoryBuilder.ClassFactory<CacheWriter<? super PageKey, ? super Page>>(properties.getProperty("cache-writer-factory")));
+            }
+            final String expirtyPolicy = properties.getProperty("expiry-policy-factory");
+            if (expirtyPolicy != null)
+            {
+                configuration.setExpiryPolicyFactory(new FactoryBuilder.ClassFactory<ExpiryPolicy>(expirtyPolicy));
+            }
+            configuration.setManagementEnabled("true".equals(properties.getProperty("management-enabled", "false")));
+            configuration.setStatisticsEnabled("true".equals(properties.getProperty("statistics-enabled", "false")));
+            cache = manager.createCache(cacheName, configuration);
+        }
+    }
+
+    @Override
+    public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException
+    {
+        boolean gzip = false;
+        if (HttpServletRequest.class.isInstance(servletRequest))
+        {
+            final Enumeration<String> acceptEncoding = HttpServletRequest.class.cast(servletRequest).getHeaders("Accept-Encoding");
+            while (acceptEncoding != null && acceptEncoding.hasMoreElements())
+            {
+                if ("gzip".equals(acceptEncoding.nextElement()))
+                {
+                    gzip = true;
+                    break;
+                }
+            }
+        }
+
+        final HttpServletResponse httpServletResponse = HttpServletResponse.class.cast(servletResponse);
+        checkResponse(httpServletResponse);
+
+        final PageKey key = new PageKey(key(servletRequest), gzip);
+        Page page = cache.get(key);
+        if (page == null)
+        {
+            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            final InMemoryResponse response;
+            if (gzip)
+            {
+                response = new InMemoryResponse(httpServletResponse, new GZIPOutputStream(baos));
+            }
+            else
+            {
+                response = new InMemoryResponse(httpServletResponse, baos);
+            }
+            filterChain.doFilter(servletRequest, response);
+            response.flushBuffer();
+
+            page = new Page(
+                    response.getStatus(),
+                    response.getContentType(),
+                    response.getContentLength(),
+                    response.getCookies(),
+                    response.getHeaders(),
+                    baos.toByteArray());
+            cache.put(key, page);
+        }
+
+        if (page.status == SC_OK) {
+            checkResponse(httpServletResponse);
+
+            if (gzip)
+            {
+                httpServletResponse.setHeader("Content-Encoding", "gzip");
+            }
+
+            httpServletResponse.setStatus(page.status);
+            if (page.contentType != null)
+            {
+                httpServletResponse.setContentType(page.contentType);
+            }
+            if (page.contentLength > 0)
+            {
+                httpServletResponse.setContentLength(page.contentLength);
+            }
+            for (final Cookie c : page.cookies)
+            {
+                httpServletResponse.addCookie(c);
+            }
+            for (final Map.Entry<String, List<Serializable>> entry : page.headers.entrySet())
+            {
+                for (final Serializable value : entry.getValue())
+                {
+                    if (Integer.class.isInstance(value))
+                    {
+                        httpServletResponse.addIntHeader(entry.getKey(), Integer.class.cast(value));
+                    }
+                    else if (String.class.isInstance(value))
+                    {
+                        httpServletResponse.addHeader(entry.getKey(), String.class.cast(value));
+                    }
+                    else if (Long.class.isInstance(value))
+                    {
+                        httpServletResponse.addDateHeader(entry.getKey(), Long.class.cast(value));
+                    }
+                }
+            }
+            httpServletResponse.setContentLength(page.out.length);
+            final BufferedOutputStream bos = new BufferedOutputStream(httpServletResponse.getOutputStream());
+            if (page.out.length != 0)
+            {
+                bos.write(page.out);
+            }
+            else
+            {
+                bos.write(new byte[0]);
+            }
+            bos.flush();
+        }
+    }
+
+    protected String key(final ServletRequest servletRequest)
+    {
+        if (HttpServletRequest.class.isInstance(servletRequest))
+        {
+            final HttpServletRequest request = HttpServletRequest.class.cast(servletRequest);
+            return request.getMethod() + '_' + request.getRequestURI() + '_' + request.getQueryString();
+        }
+        return servletRequest.toString();
+    }
+
+    private void checkResponse(final ServletResponse servletResponse)
+    {
+        if (servletResponse.isCommitted()) {
+            throw new IllegalStateException("Response committed");
+        }
+    }
+
+    @Override
+    public void destroy()
+    {
+        if (!cache.isClosed())
+        {
+            cache.close();
+        }
+        if (!manager.isClosed())
+        {
+            manager.close();
+        }
+        provider.close();
+    }
+
+    protected static class PageKey implements Serializable {
+        private final String uri;
+        private boolean gzip;
+
+        public PageKey(final String uri, final boolean gzip)
+        {
+            this.uri = uri;
+            this.gzip = gzip;
+        }
+
+        public void setGzip(final boolean gzip)
+        {
+            this.gzip = gzip;
+        }
+
+        @Override
+        public boolean equals(final Object o)
+        {
+            if (this == o)
+            {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass())
+            {
+                return false;
+            }
+
+            final PageKey pageKey = PageKey.class.cast(o);
+            return gzip == pageKey.gzip && uri.equals(pageKey.uri);
+
+        }
+
+        @Override
+        public int hashCode()
+        {
+            int result = uri.hashCode();
+            result = 31 * result + (gzip ? 1 : 0);
+            return result;
+        }
+    }
+
+    protected static class Page implements Serializable {
+        private final int status;
+        private final String contentType;
+        private final int contentLength;
+        private final Collection<Cookie> cookies;
+        private final Map<String, List<Serializable>> headers;
+        private final byte[] out;
+
+        public Page(final int status,
+                    final String contentType, final int contentLength,
+                    final Collection<Cookie> cookies, final Map<String, List<Serializable>> headers,
+                    final byte[] out)
+        {
+            this.status = status;
+            this.contentType = contentType;
+            this.contentLength = contentLength;
+            this.cookies = cookies;
+            this.headers = headers;
+            this.out = out;
+        }
+
+        @Override
+        public boolean equals(final Object o)
+        {
+            if (this == o)
+            {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass())
+            {
+                return false;
+            }
+
+            final Page page = Page.class.cast(o);
+            return contentLength == page.contentLength
+                    && status == page.status
+                    && !(contentType != null ? !contentType.equals(page.contentType) : page.contentType != null)
+                    && cookies.equals(page.cookies)
+                    && headers.equals(page.headers)
+                    && Arrays.equals(out, page.out);
+
+        }
+
+        @Override
+        public int hashCode()
+        {
+            int result = status;
+            result = 31 * result + (contentType != null ? contentType.hashCode() : 0);
+            result = 31 * result + contentLength;
+            result = 31 * result + cookies.hashCode();
+            result = 31 * result + headers.hashCode();
+            result = 31 * result + Arrays.hashCode(out);
+            return result;
+        }
+    }
+}
diff --git a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/writer/AsyncCacheWriter.java b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/writer/AsyncCacheWriter.java
new file mode 100644
index 0000000..0abc65b
--- /dev/null
+++ b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/writer/AsyncCacheWriter.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.extras.writer;
+
+import javax.cache.Cache;
+import javax.cache.configuration.Factory;
+import javax.cache.integration.CacheWriter;
+import javax.cache.integration.CacheWriterException;
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class AsyncCacheWriter<K, V> implements CacheWriter<K, V>, Closeable, Factory<CacheWriter<K, V>>
+{
+    private static final Logger LOGGER = Logger.getLogger(AsyncCacheWriter.class.getName());
+
+    private final CacheWriter<K, V> writer;
+    private final ExecutorService pool;
+
+    public AsyncCacheWriter(final CacheWriter<K, V> delegate, final int poolSize)
+    {
+        writer = delegate;
+        pool = Executors.newFixedThreadPool(
+                poolSize, new DaemonThreadFactory(delegate.getClass().getName() + "-" + delegate.hashCode() + "-"));
+    }
+
+    @Override
+    public void write(final Cache.Entry<? extends K, ? extends V> entry) throws CacheWriterException
+    {
+        pool.submit(new ExceptionProtectionRunnable()
+        {
+            @Override
+            public void doRun()
+            {
+                writer.write(entry);
+            }
+        });
+    }
+
+    @Override
+    public void writeAll(final Collection<Cache.Entry<? extends K, ? extends V>> entries) throws CacheWriterException
+    {
+        pool.submit(new ExceptionProtectionRunnable()
+        {
+            @Override
+            public void doRun()
+            {
+                writer.writeAll(entries);
+            }
+        });
+    }
+
+    @Override
+    public void delete(final Object key) throws CacheWriterException
+    {
+        pool.submit(new ExceptionProtectionRunnable()
+        {
+            @Override
+            public void doRun()
+            {
+                writer.delete(key);
+            }
+        });
+    }
+
+    @Override
+    public void deleteAll(final Collection<?> keys) throws CacheWriterException
+    {
+        pool.submit(new ExceptionProtectionRunnable()
+        {
+            @Override
+            public void doRun()
+            {
+                writer.deleteAll(keys);
+            }
+        });
+    }
+
+    @Override
+    public void close() throws IOException
+    {
+        final List<Runnable> runnables = pool.shutdownNow();
+        for (final Runnable r : runnables)
+        {
+            r.run();
+        }
+    }
+
+    @Override
+    public CacheWriter<K, V> create()
+    {
+        return this;
+    }
+
+    // avoid dep on impl
+    private static class DaemonThreadFactory implements ThreadFactory
+    {
+        private final AtomicInteger index = new AtomicInteger(1);
+        private final String prefix;
+
+        public DaemonThreadFactory(final String prefix)
+        {
+            this.prefix = prefix;
+        }
+
+        @Override
+        public Thread newThread( final Runnable runner )
+        {
+            final Thread t = new Thread( runner );
+            t.setName(prefix + index.getAndIncrement());
+            t.setDaemon(true);
+            return t;
+        }
+    }
+
+    private static abstract class ExceptionProtectionRunnable implements Runnable
+    {
+        @Override
+        public void run()
+        {
+            try
+            {
+                doRun();
+            }
+            catch (final Exception e)
+            {
+                LOGGER.log(Level.SEVERE, e.getMessage(), e);
+            }
+        }
+
+        protected abstract void doRun();
+    }
+}
diff --git a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/writer/CacheWriterAdapter.java b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/writer/CacheWriterAdapter.java
new file mode 100644
index 0000000..cbcd3e5
--- /dev/null
+++ b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/writer/CacheWriterAdapter.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.extras.writer;
+
+import javax.cache.Cache;
+import javax.cache.configuration.Factory;
+import javax.cache.integration.CacheWriter;
+import javax.cache.integration.CacheWriterException;
+import java.util.Collection;
+
+public abstract class CacheWriterAdapter<K, V> implements CacheWriter<K, V>, Factory<CacheWriter<K, V>>
+{
+    @Override
+    public void writeAll(final Collection<Cache.Entry<? extends K, ? extends V>> entries) throws CacheWriterException
+    {
+        for (final Cache.Entry<? extends K, ? extends V> entry : entries)
+        {
+            write(entry);
+        }
+    }
+
+    @Override
+    public void deleteAll(final Collection<?> keys) throws CacheWriterException
+    {
+        for (final Object k : keys)
+        {
+            delete(k);
+        }
+    }
+
+    @Override
+    public CacheWriter<K, V> create()
+    {
+        return this;
+    }
+}
diff --git a/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/writer/CompositeCacheWriter.java b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/writer/CompositeCacheWriter.java
new file mode 100644
index 0000000..3fad841
--- /dev/null
+++ b/commons-jcs-jcache-extras/src/main/java/org/apache/commons/jcs3/jcache/extras/writer/CompositeCacheWriter.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.extras.writer;
+
+import javax.cache.Cache;
+import javax.cache.configuration.Factory;
+import javax.cache.integration.CacheWriter;
+import javax.cache.integration.CacheWriterException;
+
+import org.apache.commons.jcs3.jcache.extras.closeable.Closeables;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.Collection;
+
+public class CompositeCacheWriter<K, V> implements CacheWriter<K, V>, Closeable, Factory<CacheWriter<K, V>>
+{
+    private final CacheWriter<K, V>[] writers;
+
+    public CompositeCacheWriter(final CacheWriter<K, V>... writers)
+    {
+        this.writers = writers;
+    }
+
+    @Override
+    public void write(final Cache.Entry<? extends K, ? extends V> entry) throws CacheWriterException
+    {
+        CacheWriterException e = null;
+        for (final CacheWriter<K, V> writer : writers)
+        {
+            try
+            {
+                writer.write(entry);
+            }
+            catch (final CacheWriterException ex)
+            {
+                if (e == null)
+                {
+                    e = ex;
+                }
+            }
+        }
+        if (e != null)
+        {
+            throw e;
+        }
+    }
+
+    @Override
+    public void writeAll(final Collection<Cache.Entry<? extends K, ? extends V>> entries) throws CacheWriterException
+    {
+        CacheWriterException e = null;
+        for (final CacheWriter<K, V> writer : writers)
+        {
+            try
+            {
+                writer.writeAll(entries);
+            }
+            catch (final CacheWriterException ex)
+            {
+                if (e == null)
+                {
+                    e = ex;
+                }
+            }
+        }
+        if (e != null)
+        {
+            throw e;
+        }
+    }
+
+    @Override
+    public void delete(final Object key) throws CacheWriterException
+    {
+        CacheWriterException e = null;
+        for (final CacheWriter<K, V> writer : writers)
+        {
+            try
+            {
+                writer.delete(key);
+            }
+            catch (final CacheWriterException ex)
+            {
+                if (e == null)
+                {
+                    e = ex;
+                }
+            }
+        }
+        if (e != null)
+        {
+            throw e;
+        }
+    }
+
+    @Override
+    public void deleteAll(final Collection<?> keys) throws CacheWriterException
+    {
+        CacheWriterException e = null;
+        for (final CacheWriter<K, V> writer : writers)
+        {
+            try
+            {
+                writer.deleteAll(keys);
+            }
+            catch (final CacheWriterException ex)
+            {
+                if (e == null)
+                {
+                    e = ex;
+                }
+            }
+        }
+        if (e != null)
+        {
+            throw e;
+        }
+    }
+
+    @Override
+    public CacheWriter<K, V> create()
+    {
+        return this;
+    }
+
+    @Override
+    public void close() throws IOException
+    {
+        Closeables.close(writers);
+    }
+}
diff --git a/commons-jcs-jcache-extras/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/commons-jcs-jcache-extras/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
index 287f237..5abd701 100644
--- a/commons-jcs-jcache-extras/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
+++ b/commons-jcs-jcache-extras/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
@@ -1 +1,18 @@
-org.apache.commons.jcs.jcache.extras.cdi.ExtraJCacheExtension
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+org.apache.commons.jcs3.jcache.extras.cdi.ExtraJCacheExtension
diff --git a/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs/jcache/extras/InternalCacheRule.java b/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs/jcache/extras/InternalCacheRule.java
deleted file mode 100644
index f02c2f3..0000000
--- a/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs/jcache/extras/InternalCacheRule.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.extras;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-import javax.cache.Cache;
-import javax.cache.CacheManager;
-import javax.cache.Caching;
-import javax.cache.configuration.CompleteConfiguration;
-import javax.cache.configuration.Configuration;
-import javax.cache.spi.CachingProvider;
-import java.lang.reflect.Field;
-
-// TODO: *if needed* define @CacheDeifnition instead of relying on field types
-public class InternalCacheRule implements TestRule
-{
-    private final Object test;
-
-    public InternalCacheRule(final Object test)
-    {
-        this.test = test;
-    }
-
-    @Override
-    public Statement apply(final Statement base, final Description description)
-    {
-        return new Statement()
-        {
-            @Override
-            public void evaluate() throws Throwable
-            {
-                final CachingProvider provider = Caching.getCachingProvider();
-                final CacheManager manager = provider.getCacheManager();
-                try
-                {
-                    Field cache = null;
-                    CompleteConfiguration<?, ?> config = null;
-                    for (final Field f : test.getClass().getDeclaredFields())
-                    {
-                        if (Cache.class.isAssignableFrom(f.getType()))
-                        {
-                            f.setAccessible(true);
-                            cache = f;
-                        }
-                        else if (Configuration.class.isAssignableFrom(f.getType()))
-                        {
-                            f.setAccessible(true);
-                            config = (CompleteConfiguration<?, ?>) f.get(test);
-                        }
-                    }
-                    if (cache != null)
-                    {
-                        if (config == null)
-                        {
-                            throw new IllegalStateException("Define a Configuration field");
-                        }
-                        cache.set(test, manager.createCache(cache.getName(), config));
-                    }
-                    base.evaluate();
-                }
-                finally
-                {
-                    manager.close();
-                    provider.close();
-                }
-            }
-        };
-    }
-}
diff --git a/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs/jcache/extras/cdi/ExtraJCacheExtensionTest.java b/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs/jcache/extras/cdi/ExtraJCacheExtensionTest.java
deleted file mode 100644
index e3ae271..0000000
--- a/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs/jcache/extras/cdi/ExtraJCacheExtensionTest.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.extras.cdi;
-
-import org.apache.webbeans.config.WebBeansContext;
-import org.apache.webbeans.container.BeanManagerImpl;
-import org.apache.webbeans.inject.OWBInjector;
-import org.apache.webbeans.spi.ContainerLifecycle;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-import javax.cache.CacheManager;
-import javax.cache.spi.CachingProvider;
-import javax.inject.Inject;
-
-import static org.junit.Assert.assertNotNull;
-
-public class ExtraJCacheExtensionTest
-{
-    private static BeanManagerImpl bm;
-    private static ContainerLifecycle lifecycle;
-
-    @BeforeClass
-    public static void startContainer()
-    {
-        final WebBeansContext webBeansContext = WebBeansContext.currentInstance();
-        lifecycle = webBeansContext.getService(ContainerLifecycle.class);
-        lifecycle.startApplication(null);
-        bm = webBeansContext.getBeanManagerImpl();
-    }
-
-    @AfterClass
-    public static void stopContainer()
-    {
-        lifecycle.stopApplication(null);
-    }
-
-    @Before
-    public void inject() throws Exception
-    {
-        OWBInjector.inject(bm, this, bm.createCreationalContext(null));
-    }
-
-    @Inject
-    private BeanWithInjections bean;
-
-    @Test
-    public void defaultCacheManager()
-    {
-        assertNotNull(bean.getMgr());
-    }
-
-    @Test
-    public void defaultCacheProvider()
-    {
-        assertNotNull(bean.getProvider());
-    }
-
-    public static class BeanWithInjections {
-        @Inject
-        private CacheManager mgr;
-
-        @Inject
-        private CachingProvider provider;
-
-        public CacheManager getMgr()
-        {
-            return mgr;
-        }
-
-        public CachingProvider getProvider()
-        {
-            return provider;
-        }
-    }
-}
diff --git a/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs/jcache/extras/loader/CacheLoaderAdapterTest.java b/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs/jcache/extras/loader/CacheLoaderAdapterTest.java
deleted file mode 100644
index 3c6767b..0000000
--- a/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs/jcache/extras/loader/CacheLoaderAdapterTest.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.extras.loader;
-
-import org.apache.commons.jcs.jcache.extras.InternalCacheRule;
-import org.junit.Rule;
-import org.junit.Test;
-
-import javax.cache.Cache;
-import javax.cache.configuration.Configuration;
-import javax.cache.configuration.MutableConfiguration;
-import javax.cache.integration.CacheLoaderException;
-import java.util.HashSet;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import static java.util.Arrays.asList;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-
-public class CacheLoaderAdapterTest
-{
-    @Rule
-    public final InternalCacheRule rule = new InternalCacheRule(this);
-
-    private final AtomicInteger count = new AtomicInteger(0);
-    private final Configuration<?, ?> config = new MutableConfiguration<String, String>().setStoreByValue(false).setReadThrough(true).setCacheLoaderFactory(new CacheLoaderAdapter<String, String>()
-    {
-        @Override
-        public String load(final String key) throws CacheLoaderException
-        {
-            count.incrementAndGet();
-            return key;
-        }
-    });
-    private Cache<String, String> cache;
-
-    @Test
-    public void checkLoadAll()
-    {
-        assertFalse(cache.iterator().hasNext());
-        assertEquals("foo", cache.get("foo"));
-
-        count.decrementAndGet();
-        cache.loadAll(new HashSet<String>(asList("a", "b")), true, null);
-        int retries = 100;
-        while (retries-- > 0 && count.get() != 2)
-        {
-            try
-            {
-                Thread.sleep(20);
-            }
-            catch (final InterruptedException e)
-            {
-                Thread.interrupted();
-            }
-        }
-        assertEquals(2, count.get());
-        assertEquals("a", cache.get("a"));
-        assertEquals("b", cache.get("b"));
-        assertEquals(2, count.get());
-    }
-}
diff --git a/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs/jcache/extras/loader/CompositeCacheLoaderTest.java b/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs/jcache/extras/loader/CompositeCacheLoaderTest.java
deleted file mode 100644
index 08d1d7a..0000000
--- a/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs/jcache/extras/loader/CompositeCacheLoaderTest.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.extras.loader;
-
-import org.apache.commons.jcs.jcache.extras.InternalCacheRule;
-import org.junit.Rule;
-import org.junit.Test;
-
-import javax.cache.Cache;
-import javax.cache.configuration.Configuration;
-import javax.cache.configuration.MutableConfiguration;
-import javax.cache.integration.CacheLoaderException;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import static org.junit.Assert.assertEquals;
-
-public class CompositeCacheLoaderTest
-{
-    @Rule
-    public final InternalCacheRule rule = new InternalCacheRule(this);
-
-    private final AtomicInteger count = new AtomicInteger(0);
-
-    private final CacheLoaderAdapter<String, String> loader1 = new CacheLoaderAdapter<String, String>()
-    {
-        @Override
-        public String load(String key) throws CacheLoaderException
-        {
-            count.incrementAndGet();
-            return null;
-        }
-    };
-    private final CacheLoaderAdapter<String, String> loader2 = new CacheLoaderAdapter<String, String>()
-    {
-        @Override
-        public String load(String key) throws CacheLoaderException
-        {
-            count.incrementAndGet();
-            return null;
-        }
-    };
-    private final Configuration<?, ?> config = new MutableConfiguration<String, String>()
-            .setStoreByValue(false)
-            .setReadThrough(true)
-            .setCacheLoaderFactory(new CompositeCacheLoader<String, String>(loader1, loader2));
-    private Cache<String, String> cache;
-
-    @Test
-    public void checkComposite()
-    {
-        cache.get("foo");
-        assertEquals(2, count.get());
-    }
-}
diff --git a/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs/jcache/extras/web/JCacheFilterTest.java b/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs/jcache/extras/web/JCacheFilterTest.java
deleted file mode 100644
index 3c514d1..0000000
--- a/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs/jcache/extras/web/JCacheFilterTest.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.extras.web;
-
-import static org.junit.Assert.assertEquals;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.URL;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.apache.catalina.Context;
-import org.apache.catalina.LifecycleException;
-import org.apache.catalina.LifecycleState;
-import org.apache.catalina.core.StandardContext;
-import org.apache.catalina.deploy.FilterDef;
-import org.apache.catalina.deploy.FilterMap;
-import org.apache.catalina.startup.Tomcat;
-import org.apache.commons.io.IOUtils;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-public class JCacheFilterTest
-{
-    private static File docBase;
-
-    @BeforeClass
-    public static void createEmptyDir() {
-        docBase = new File("target/missing/");
-        docBase.mkdirs();
-        docBase.deleteOnExit();
-    }
-
-    @Test
-    public void testFilterNoOutput() throws Exception
-    {
-        Empty.COUNTER.set(0);
-        final Tomcat tomcat = new Tomcat();
-        tomcat.setHostname("localhost");
-        tomcat.setPort(0);
-        try {
-            tomcat.getEngine();
-            tomcat.start();
-            final Context ctx = tomcat.addWebapp("/sample", docBase.getAbsolutePath());
-            Tomcat.addServlet(ctx, "empty", Empty.class.getName());
-            ctx.addServletMapping("/", "empty");
-            addJcsFilter(ctx);
-            StandardContext.class.cast(ctx).filterStart();
-
-            final URL url = new URL("http://localhost:" + tomcat.getConnector().getLocalPort() + "/sample/");
-
-            assertEquals("", IOUtils.toString(url.openStream()));
-            assertEquals(1, Empty.COUNTER.get());
-
-            assertEquals("", IOUtils.toString(url.openStream()));
-            assertEquals(1, Empty.COUNTER.get());
-        } finally {
-            stop(tomcat);
-        }
-    }
-
-    @Test
-    public void testFilter() throws Exception
-    {
-        Hello.COUNTER.set(0);
-        final Tomcat tomcat = new Tomcat();
-        tomcat.setPort(0);
-        try {
-            tomcat.getEngine();
-            tomcat.start();
-            final Context ctx = tomcat.addContext("/sample", docBase.getAbsolutePath());
-            Tomcat.addServlet(ctx, "hello", Hello.class.getName());
-            ctx.addServletMapping("/", "hello");
-            addJcsFilter(ctx);
-            StandardContext.class.cast(ctx).filterStart();
-
-            final URL url = new URL("http://localhost:" + tomcat.getConnector().getLocalPort() + "/sample/");
-            assertEquals("hello", IOUtils.toString(url.openStream()));
-            assertEquals(1, Hello.COUNTER.get());
-
-            assertEquals("hello", IOUtils.toString(url.openStream()));
-            assertEquals(1, Hello.COUNTER.get());
-        } finally {
-            stop(tomcat);
-        }
-    }
-
-    private void stop(final Tomcat tomcat) throws LifecycleException {
-        if (LifecycleState.STARTED.equals(tomcat.getServer().getState())) {
-            tomcat.stop();
-            tomcat.destroy();
-        }
-    }
-
-    private void addJcsFilter(final Context ctx) {
-        final FilterDef filterDef = new FilterDef();
-        filterDef.setFilterName("jcs");
-        filterDef.setFilterClass(JCacheFilter.class.getName());
-        ctx.addFilterDef(filterDef);
-
-        final FilterMap filterMap = new FilterMap();
-        filterMap.setFilterName(filterDef.getFilterName());
-        filterMap.addURLPattern("/*");
-        ctx.addFilterMap(filterMap);
-    }
-
-    public static class Hello extends HttpServlet {
-        public static final AtomicInteger COUNTER = new AtomicInteger();
-
-        @Override
-        protected void service(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
-            resp.getWriter().write("hello");
-            COUNTER.incrementAndGet();
-        }
-    }
-
-    public static class Empty extends HttpServlet {
-        public static final AtomicInteger COUNTER = new AtomicInteger();
-
-        @Override
-        protected void service(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
-            resp.getWriter().write("");
-            COUNTER.incrementAndGet();
-        }
-    }
-}
diff --git a/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs/jcache/extras/writer/CacheWriterAdapterTest.java b/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs/jcache/extras/writer/CacheWriterAdapterTest.java
deleted file mode 100644
index 190edc1..0000000
--- a/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs/jcache/extras/writer/CacheWriterAdapterTest.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.extras.writer;
-
-import org.apache.commons.jcs.jcache.extras.InternalCacheRule;
-import org.junit.Rule;
-import org.junit.Test;
-
-import javax.cache.Cache;
-import javax.cache.configuration.Configuration;
-import javax.cache.configuration.MutableConfiguration;
-import javax.cache.integration.CacheWriterException;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-
-import static java.util.Arrays.asList;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-public class CacheWriterAdapterTest
-{
-    @Rule
-    public final InternalCacheRule rule = new InternalCacheRule(this);
-
-    private final Map<String, String> copy = new HashMap<String, String>();
-    private final Configuration<?, ?> config = new MutableConfiguration<String, String>()
-            .setStoreByValue(false).setReadThrough(true)
-            .setCacheWriterFactory(new CacheWriterAdapter<String, String>()
-            {
-                @Override
-                public void write(final Cache.Entry<? extends String, ? extends String> entry) throws CacheWriterException
-                {
-                    copy.put(entry.getKey(), entry.getValue());
-                }
-
-                @Override
-                public void delete(final Object key) throws CacheWriterException
-                {
-                    copy.remove(key);
-                }
-            });
-    private Cache<String, String> cache;
-
-    @Test
-    public void checkWriteAllAndDeleteAll()
-    {
-        assertTrue(copy.isEmpty());
-        assertFalse(cache.iterator().hasNext());
-        cache.put("foo", "bar");
-        assertEquals(1, copy.size());
-        cache.remove("foo");
-        assertTrue(copy.isEmpty());
-
-        cache.putAll(new HashMap<String, String>() {{
-            put("a", "b");
-            put("b", "c");
-        }});
-        assertEquals(2, copy.size());
-        cache.removeAll(new HashSet<String>(asList("a", "b")));
-        assertTrue(copy.isEmpty());
-    }
-}
diff --git a/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs/jcache/extras/writer/CompositeCacheWriterTest.java b/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs/jcache/extras/writer/CompositeCacheWriterTest.java
deleted file mode 100644
index 1ba9612..0000000
--- a/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs/jcache/extras/writer/CompositeCacheWriterTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.extras.writer;
-
-import org.apache.commons.jcs.jcache.extras.InternalCacheRule;
-import org.junit.Rule;
-import org.junit.Test;
-
-import javax.cache.Cache;
-import javax.cache.configuration.Configuration;
-import javax.cache.configuration.MutableConfiguration;
-import javax.cache.integration.CacheWriterException;
-import java.util.HashMap;
-import java.util.Map;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-public class CompositeCacheWriterTest
-{
-    @Rule
-    public final InternalCacheRule rule = new InternalCacheRule(this);
-
-
-    private final Map<String, String> copy1 = new HashMap<String, String>();
-    private final Map<String, String> copy2 = new HashMap<String, String>();
-
-    private final CacheWriterAdapter<String, String> writer1 = new CacheWriterAdapter<String, String>()
-    {
-        @Override
-        public void write(final Cache.Entry<? extends String, ? extends String> entry) throws CacheWriterException
-        {
-            copy1.put(entry.getKey(), entry.getValue());
-        }
-
-        @Override
-        public void delete(final Object key) throws CacheWriterException
-        {
-            copy1.remove(key);
-        }
-    };
-    private final CacheWriterAdapter<String, String> writer2 = new CacheWriterAdapter<String, String>()
-    {
-        @Override
-        public void write(final Cache.Entry<? extends String, ? extends String> entry) throws CacheWriterException
-        {
-            copy2.put(entry.getKey(), entry.getValue());
-        }
-
-        @Override
-        public void delete(final Object key) throws CacheWriterException
-        {
-            copy2.remove(key);
-        }
-    };
-    private final Configuration<?, ?> config = new MutableConfiguration<String, String>()
-            .setStoreByValue(false)
-            .setWriteThrough(true)
-            .setCacheWriterFactory(new CompositeCacheWriter<String, String>(writer1, writer2));
-    private Cache<String, String> cache;
-
-    @Test
-    public void checkComposite()
-    {
-        cache.put("a", "b");
-        assertEquals("b", copy1.get("a"));
-        assertEquals("b", copy2.get("a"));
-        assertEquals(1, copy1.size());
-        assertEquals(1, copy2.size());
-        cache.remove("a");
-        assertTrue(copy1.isEmpty());
-        assertTrue(copy2.isEmpty());
-    }
-}
diff --git a/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs3/jcache/extras/InternalCacheRule.java b/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs3/jcache/extras/InternalCacheRule.java
new file mode 100644
index 0000000..cc34f97
--- /dev/null
+++ b/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs3/jcache/extras/InternalCacheRule.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.extras;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import javax.cache.Cache;
+import javax.cache.CacheManager;
+import javax.cache.Caching;
+import javax.cache.configuration.CompleteConfiguration;
+import javax.cache.configuration.Configuration;
+import javax.cache.spi.CachingProvider;
+import java.lang.reflect.Field;
+
+// TODO: *if needed* define @CacheDeifnition instead of relying on field types
+public class InternalCacheRule implements TestRule
+{
+    private final Object test;
+
+    public InternalCacheRule(final Object test)
+    {
+        this.test = test;
+    }
+
+    @Override
+    public Statement apply(final Statement base, final Description description)
+    {
+        return new Statement()
+        {
+            @Override
+            public void evaluate() throws Throwable
+            {
+                final CachingProvider provider = Caching.getCachingProvider();
+                final CacheManager manager = provider.getCacheManager();
+                try
+                {
+                    Field cache = null;
+                    CompleteConfiguration<?, ?> config = null;
+                    for (final Field f : test.getClass().getDeclaredFields())
+                    {
+                        if (Cache.class.isAssignableFrom(f.getType()))
+                        {
+                            f.setAccessible(true);
+                            cache = f;
+                        }
+                        else if (Configuration.class.isAssignableFrom(f.getType()))
+                        {
+                            f.setAccessible(true);
+                            config = (CompleteConfiguration<?, ?>) f.get(test);
+                        }
+                    }
+                    if (cache != null)
+                    {
+                        if (config == null)
+                        {
+                            throw new IllegalStateException("Define a Configuration field");
+                        }
+                        cache.set(test, manager.createCache(cache.getName(), config));
+                    }
+                    base.evaluate();
+                }
+                finally
+                {
+                    manager.close();
+                    provider.close();
+                }
+            }
+        };
+    }
+}
diff --git a/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs3/jcache/extras/cdi/ExtraJCacheExtensionTest.java b/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs3/jcache/extras/cdi/ExtraJCacheExtensionTest.java
new file mode 100644
index 0000000..08ac990
--- /dev/null
+++ b/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs3/jcache/extras/cdi/ExtraJCacheExtensionTest.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.extras.cdi;
+
+import org.apache.webbeans.config.WebBeansContext;
+import org.apache.webbeans.container.BeanManagerImpl;
+import org.apache.webbeans.inject.OWBInjector;
+import org.apache.webbeans.spi.ContainerLifecycle;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import javax.cache.CacheManager;
+import javax.cache.spi.CachingProvider;
+import javax.inject.Inject;
+
+import static org.junit.Assert.assertNotNull;
+
+public class ExtraJCacheExtensionTest
+{
+    private static BeanManagerImpl bm;
+    private static ContainerLifecycle lifecycle;
+
+    @BeforeClass
+    public static void startContainer()
+    {
+        final WebBeansContext webBeansContext = WebBeansContext.currentInstance();
+        lifecycle = webBeansContext.getService(ContainerLifecycle.class);
+        lifecycle.startApplication(null);
+        bm = webBeansContext.getBeanManagerImpl();
+    }
+
+    @AfterClass
+    public static void stopContainer()
+    {
+        lifecycle.stopApplication(null);
+    }
+
+    @Before
+    public void inject() throws Exception
+    {
+        OWBInjector.inject(bm, this, bm.createCreationalContext(null));
+    }
+
+    @Inject
+    private BeanWithInjections bean;
+
+    @Test
+    public void defaultCacheManager()
+    {
+        assertNotNull(bean.getMgr());
+    }
+
+    @Test
+    public void defaultCacheProvider()
+    {
+        assertNotNull(bean.getProvider());
+    }
+
+    public static class BeanWithInjections {
+        @Inject
+        private CacheManager mgr;
+
+        @Inject
+        private CachingProvider provider;
+
+        public CacheManager getMgr()
+        {
+            return mgr;
+        }
+
+        public CachingProvider getProvider()
+        {
+            return provider;
+        }
+    }
+}
diff --git a/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs3/jcache/extras/loader/CacheLoaderAdapterTest.java b/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs3/jcache/extras/loader/CacheLoaderAdapterTest.java
new file mode 100644
index 0000000..8494462
--- /dev/null
+++ b/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs3/jcache/extras/loader/CacheLoaderAdapterTest.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.extras.loader;
+
+import org.apache.commons.jcs3.jcache.extras.InternalCacheRule;
+import org.apache.commons.jcs3.jcache.extras.loader.CacheLoaderAdapter;
+import org.junit.Rule;
+import org.junit.Test;
+
+import javax.cache.Cache;
+import javax.cache.configuration.Configuration;
+import javax.cache.configuration.MutableConfiguration;
+import javax.cache.integration.CacheLoaderException;
+import java.util.HashSet;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static java.util.Arrays.asList;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+public class CacheLoaderAdapterTest
+{
+    @Rule
+    public final InternalCacheRule rule = new InternalCacheRule(this);
+
+    private final AtomicInteger count = new AtomicInteger(0);
+    private final Configuration<?, ?> config = new MutableConfiguration<String, String>().setStoreByValue(false).setReadThrough(true).setCacheLoaderFactory(new CacheLoaderAdapter<String, String>()
+    {
+        @Override
+        public String load(final String key) throws CacheLoaderException
+        {
+            count.incrementAndGet();
+            return key;
+        }
+    });
+    private Cache<String, String> cache;
+
+    @Test
+    public void checkLoadAll()
+    {
+        assertFalse(cache.iterator().hasNext());
+        assertEquals("foo", cache.get("foo"));
+
+        count.decrementAndGet();
+        cache.loadAll(new HashSet<String>(asList("a", "b")), true, null);
+        int retries = 100;
+        while (retries-- > 0 && count.get() != 2)
+        {
+            try
+            {
+                Thread.sleep(20);
+            }
+            catch (final InterruptedException e)
+            {
+                Thread.interrupted();
+            }
+        }
+        assertEquals(2, count.get());
+        assertEquals("a", cache.get("a"));
+        assertEquals("b", cache.get("b"));
+        assertEquals(2, count.get());
+    }
+}
diff --git a/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs3/jcache/extras/loader/CompositeCacheLoaderTest.java b/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs3/jcache/extras/loader/CompositeCacheLoaderTest.java
new file mode 100644
index 0000000..872d54c
--- /dev/null
+++ b/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs3/jcache/extras/loader/CompositeCacheLoaderTest.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.extras.loader;
+
+import org.apache.commons.jcs3.jcache.extras.InternalCacheRule;
+import org.apache.commons.jcs3.jcache.extras.loader.CacheLoaderAdapter;
+import org.apache.commons.jcs3.jcache.extras.loader.CompositeCacheLoader;
+import org.junit.Rule;
+import org.junit.Test;
+
+import javax.cache.Cache;
+import javax.cache.configuration.Configuration;
+import javax.cache.configuration.MutableConfiguration;
+import javax.cache.integration.CacheLoaderException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.junit.Assert.assertEquals;
+
+public class CompositeCacheLoaderTest
+{
+    @Rule
+    public final InternalCacheRule rule = new InternalCacheRule(this);
+
+    private final AtomicInteger count = new AtomicInteger(0);
+
+    private final CacheLoaderAdapter<String, String> loader1 = new CacheLoaderAdapter<String, String>()
+    {
+        @Override
+        public String load(String key) throws CacheLoaderException
+        {
+            count.incrementAndGet();
+            return null;
+        }
+    };
+    private final CacheLoaderAdapter<String, String> loader2 = new CacheLoaderAdapter<String, String>()
+    {
+        @Override
+        public String load(String key) throws CacheLoaderException
+        {
+            count.incrementAndGet();
+            return null;
+        }
+    };
+    private final Configuration<?, ?> config = new MutableConfiguration<String, String>()
+            .setStoreByValue(false)
+            .setReadThrough(true)
+            .setCacheLoaderFactory(new CompositeCacheLoader<String, String>(loader1, loader2));
+    private Cache<String, String> cache;
+
+    @Test
+    public void checkComposite()
+    {
+        cache.get("foo");
+        assertEquals(2, count.get());
+    }
+}
diff --git a/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs3/jcache/extras/web/JCacheFilterTest.java b/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs3/jcache/extras/web/JCacheFilterTest.java
new file mode 100644
index 0000000..9e1e5b8
--- /dev/null
+++ b/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs3/jcache/extras/web/JCacheFilterTest.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.extras.web;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.deploy.FilterDef;
+import org.apache.catalina.deploy.FilterMap;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.jcs3.jcache.extras.web.JCacheFilter;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class JCacheFilterTest
+{
+    private static File docBase;
+
+    @BeforeClass
+    public static void createEmptyDir() {
+        docBase = new File("target/missing/");
+        docBase.mkdirs();
+        docBase.deleteOnExit();
+    }
+
+    @Test
+    public void testFilterNoOutput() throws Exception
+    {
+        Empty.COUNTER.set(0);
+        final Tomcat tomcat = new Tomcat();
+        tomcat.setHostname("localhost");
+        tomcat.setPort(0);
+        try {
+            tomcat.getEngine();
+            tomcat.start();
+            final Context ctx = tomcat.addWebapp("/sample", docBase.getAbsolutePath());
+            Tomcat.addServlet(ctx, "empty", Empty.class.getName());
+            ctx.addServletMapping("/", "empty");
+            addJcsFilter(ctx);
+            StandardContext.class.cast(ctx).filterStart();
+
+            final URL url = new URL("http://localhost:" + tomcat.getConnector().getLocalPort() + "/sample/");
+
+            assertEquals("", IOUtils.toString(url.openStream()));
+            assertEquals(1, Empty.COUNTER.get());
+
+            assertEquals("", IOUtils.toString(url.openStream()));
+            assertEquals(1, Empty.COUNTER.get());
+        } finally {
+            stop(tomcat);
+        }
+    }
+
+    @Test
+    public void testFilter() throws Exception
+    {
+        Hello.COUNTER.set(0);
+        final Tomcat tomcat = new Tomcat();
+        tomcat.setPort(0);
+        try {
+            tomcat.getEngine();
+            tomcat.start();
+            final Context ctx = tomcat.addContext("/sample", docBase.getAbsolutePath());
+            Tomcat.addServlet(ctx, "hello", Hello.class.getName());
+            ctx.addServletMapping("/", "hello");
+            addJcsFilter(ctx);
+            StandardContext.class.cast(ctx).filterStart();
+
+            final URL url = new URL("http://localhost:" + tomcat.getConnector().getLocalPort() + "/sample/");
+            assertEquals("hello", IOUtils.toString(url.openStream()));
+            assertEquals(1, Hello.COUNTER.get());
+
+            assertEquals("hello", IOUtils.toString(url.openStream()));
+            assertEquals(1, Hello.COUNTER.get());
+        } finally {
+            stop(tomcat);
+        }
+    }
+
+    private void stop(final Tomcat tomcat) throws LifecycleException {
+        if (LifecycleState.STARTED.equals(tomcat.getServer().getState())) {
+            tomcat.stop();
+            tomcat.destroy();
+        }
+    }
+
+    private void addJcsFilter(final Context ctx) {
+        final FilterDef filterDef = new FilterDef();
+        filterDef.setFilterName("jcs");
+        filterDef.setFilterClass(JCacheFilter.class.getName());
+        ctx.addFilterDef(filterDef);
+
+        final FilterMap filterMap = new FilterMap();
+        filterMap.setFilterName(filterDef.getFilterName());
+        filterMap.addURLPattern("/*");
+        ctx.addFilterMap(filterMap);
+    }
+
+    public static class Hello extends HttpServlet {
+        public static final AtomicInteger COUNTER = new AtomicInteger();
+
+        @Override
+        protected void service(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
+            resp.getWriter().write("hello");
+            COUNTER.incrementAndGet();
+        }
+    }
+
+    public static class Empty extends HttpServlet {
+        public static final AtomicInteger COUNTER = new AtomicInteger();
+
+        @Override
+        protected void service(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
+            resp.getWriter().write("");
+            COUNTER.incrementAndGet();
+        }
+    }
+}
diff --git a/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs3/jcache/extras/writer/CacheWriterAdapterTest.java b/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs3/jcache/extras/writer/CacheWriterAdapterTest.java
new file mode 100644
index 0000000..5300832
--- /dev/null
+++ b/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs3/jcache/extras/writer/CacheWriterAdapterTest.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.extras.writer;
+
+import org.apache.commons.jcs3.jcache.extras.InternalCacheRule;
+import org.apache.commons.jcs3.jcache.extras.writer.CacheWriterAdapter;
+import org.junit.Rule;
+import org.junit.Test;
+
+import javax.cache.Cache;
+import javax.cache.configuration.Configuration;
+import javax.cache.configuration.MutableConfiguration;
+import javax.cache.integration.CacheWriterException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+import static java.util.Arrays.asList;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class CacheWriterAdapterTest
+{
+    @Rule
+    public final InternalCacheRule rule = new InternalCacheRule(this);
+
+    private final Map<String, String> copy = new HashMap<String, String>();
+    private final Configuration<?, ?> config = new MutableConfiguration<String, String>()
+            .setStoreByValue(false).setReadThrough(true)
+            .setCacheWriterFactory(new CacheWriterAdapter<String, String>()
+            {
+                @Override
+                public void write(final Cache.Entry<? extends String, ? extends String> entry) throws CacheWriterException
+                {
+                    copy.put(entry.getKey(), entry.getValue());
+                }
+
+                @Override
+                public void delete(final Object key) throws CacheWriterException
+                {
+                    copy.remove(key);
+                }
+            });
+    private Cache<String, String> cache;
+
+    @Test
+    public void checkWriteAllAndDeleteAll()
+    {
+        assertTrue(copy.isEmpty());
+        assertFalse(cache.iterator().hasNext());
+        cache.put("foo", "bar");
+        assertEquals(1, copy.size());
+        cache.remove("foo");
+        assertTrue(copy.isEmpty());
+
+        cache.putAll(new HashMap<String, String>() {{
+            put("a", "b");
+            put("b", "c");
+        }});
+        assertEquals(2, copy.size());
+        cache.removeAll(new HashSet<String>(asList("a", "b")));
+        assertTrue(copy.isEmpty());
+    }
+}
diff --git a/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs3/jcache/extras/writer/CompositeCacheWriterTest.java b/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs3/jcache/extras/writer/CompositeCacheWriterTest.java
new file mode 100644
index 0000000..a9b1609
--- /dev/null
+++ b/commons-jcs-jcache-extras/src/test/java/org/apache/commons/jcs3/jcache/extras/writer/CompositeCacheWriterTest.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.extras.writer;
+
+import org.apache.commons.jcs3.jcache.extras.InternalCacheRule;
+import org.apache.commons.jcs3.jcache.extras.writer.CacheWriterAdapter;
+import org.apache.commons.jcs3.jcache.extras.writer.CompositeCacheWriter;
+import org.junit.Rule;
+import org.junit.Test;
+
+import javax.cache.Cache;
+import javax.cache.configuration.Configuration;
+import javax.cache.configuration.MutableConfiguration;
+import javax.cache.integration.CacheWriterException;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class CompositeCacheWriterTest
+{
+    @Rule
+    public final InternalCacheRule rule = new InternalCacheRule(this);
+
+
+    private final Map<String, String> copy1 = new HashMap<String, String>();
+    private final Map<String, String> copy2 = new HashMap<String, String>();
+
+    private final CacheWriterAdapter<String, String> writer1 = new CacheWriterAdapter<String, String>()
+    {
+        @Override
+        public void write(final Cache.Entry<? extends String, ? extends String> entry) throws CacheWriterException
+        {
+            copy1.put(entry.getKey(), entry.getValue());
+        }
+
+        @Override
+        public void delete(final Object key) throws CacheWriterException
+        {
+            copy1.remove(key);
+        }
+    };
+    private final CacheWriterAdapter<String, String> writer2 = new CacheWriterAdapter<String, String>()
+    {
+        @Override
+        public void write(final Cache.Entry<? extends String, ? extends String> entry) throws CacheWriterException
+        {
+            copy2.put(entry.getKey(), entry.getValue());
+        }
+
+        @Override
+        public void delete(final Object key) throws CacheWriterException
+        {
+            copy2.remove(key);
+        }
+    };
+    private final Configuration<?, ?> config = new MutableConfiguration<String, String>()
+            .setStoreByValue(false)
+            .setWriteThrough(true)
+            .setCacheWriterFactory(new CompositeCacheWriter<String, String>(writer1, writer2));
+    private Cache<String, String> cache;
+
+    @Test
+    public void checkComposite()
+    {
+        cache.put("a", "b");
+        assertEquals("b", copy1.get("a"));
+        assertEquals("b", copy2.get("a"));
+        assertEquals(1, copy1.size());
+        assertEquals(1, copy2.size());
+        cache.remove("a");
+        assertTrue(copy1.isEmpty());
+        assertTrue(copy2.isEmpty());
+    }
+}
diff --git a/commons-jcs-jcache-openjpa/pom.xml b/commons-jcs-jcache-openjpa/pom.xml
index 2109498..46570df 100644
--- a/commons-jcs-jcache-openjpa/pom.xml
+++ b/commons-jcs-jcache-openjpa/pom.xml
@@ -19,13 +19,13 @@
 -->
 <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/xsd/maven-4.0.0.xsd">
   <parent>
-    <artifactId>commons-jcs</artifactId>
+    <artifactId>commons-jcs3</artifactId>
     <groupId>org.apache.commons</groupId>
     <version>3.0-SNAPSHOT</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
 
-  <artifactId>commons-jcs-jcache-openjpa</artifactId>
+  <artifactId>commons-jcs3-jcache-openjpa</artifactId>
   <version>3.0-SNAPSHOT</version>
   <name>Apache Commons JCS :: JCache OpenJPA</name>
 
@@ -66,7 +66,7 @@
     </dependency>
     <dependency>
       <groupId>org.apache.commons</groupId>
-      <artifactId>commons-jcs-jcache</artifactId>
+      <artifactId>commons-jcs3-jcache</artifactId>
       <scope>test</scope>
     </dependency>
   </dependencies>
diff --git a/commons-jcs-jcache-openjpa/src/main/java/org/apache/commons/jcs/jcache/openjpa/OpenJPAJCacheDataCache.java b/commons-jcs-jcache-openjpa/src/main/java/org/apache/commons/jcs/jcache/openjpa/OpenJPAJCacheDataCache.java
deleted file mode 100644
index 5c125c8..0000000
--- a/commons-jcs-jcache-openjpa/src/main/java/org/apache/commons/jcs/jcache/openjpa/OpenJPAJCacheDataCache.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.openjpa;
-
-import org.apache.openjpa.datacache.AbstractDataCache;
-import org.apache.openjpa.datacache.DataCacheManager;
-import org.apache.openjpa.datacache.DataCachePCData;
-import org.apache.openjpa.util.OpenJPAId;
-
-import javax.cache.Cache;
-import javax.cache.CacheManager;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-public class OpenJPAJCacheDataCache extends AbstractDataCache
-{
-    private static final String OPENJPA_PREFIX = "openjpa.datacache.";
-
-    private final Lock lock = new ReentrantLock();
-    private OpenJPAJCacheDataCacheManager manager;
-
-    @Override
-    public void initialize(final DataCacheManager manager)
-    {
-        super.initialize(manager);
-        this.manager = OpenJPAJCacheDataCacheManager.class.cast(manager);
-    }
-
-    @Override
-    protected DataCachePCData getInternal(final Object oid)
-    {
-        Object result = null;
-        if (OpenJPAId.class.isInstance(oid))
-        {
-            final Class<?> cls = OpenJPAId.class.cast(oid).getType();
-            Cache<Object, Object> cache = manager.getOrCreateCache(OPENJPA_PREFIX, cls.getName());
-            if (cache == null)
-            {
-                return null;
-            }
-            else
-            {
-                result = cache.get(oid);
-            }
-        }
-        else
-        {
-            final CacheManager cacheManager = manager.getCacheManager();
-            for (final String cacheName : cacheManager.getCacheNames())
-            {
-                if (!cacheName.startsWith(OPENJPA_PREFIX))
-                {
-                    continue;
-                }
-
-                result = cacheManager.getCache(cacheName).get(oid);
-                if (result != null)
-                {
-                    break;
-                }
-            }
-        }
-        if (result == null)
-        {
-            return null;
-        }
-        return DataCachePCData.class.cast(result);
-    }
-
-    @Override
-    protected DataCachePCData putInternal(final Object oid, final DataCachePCData pc)
-    {
-        manager.getOrCreateCache(OPENJPA_PREFIX, pc.getType().getName()).put(oid, pc);
-        return pc;
-    }
-
-    @Override
-    protected DataCachePCData removeInternal(final Object oid)
-    {
-        if (OpenJPAId.class.isInstance(oid))
-        {
-            final Object remove = manager.getOrCreateCache(OPENJPA_PREFIX, OpenJPAId.class.cast(oid).getType().getName()).getAndRemove(oid);
-            if (remove == null)
-            {
-                return null;
-            }
-            return DataCachePCData.class.cast(remove);
-        }
-        return null;
-    }
-
-    @Override
-    protected void removeAllInternal(final Class<?> cls, final boolean subclasses)
-    {
-        final String name;
-        if (subclasses)
-        {
-            name = cls.getSuperclass().getName();
-        }
-        else
-        {
-            name = cls.getName();
-        }
-        manager.getOrCreateCache(OPENJPA_PREFIX, name).removeAll();
-    }
-
-    @Override
-    protected void clearInternal()
-    {
-        final CacheManager cacheManager = manager.getCacheManager();
-        for (final String cacheName : cacheManager.getCacheNames())
-        {
-            if (!cacheName.startsWith(OPENJPA_PREFIX))
-            {
-                continue;
-            }
-            cacheManager.getCache(cacheName).clear();
-        }
-    }
-
-    @Override
-    protected boolean pinInternal(final Object oid)
-    {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    protected boolean unpinInternal(final Object oid)
-    {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void writeLock()
-    {
-        lock.lock();
-    }
-
-    @Override
-    public void writeUnlock()
-    {
-        lock.unlock();
-    }
-}
diff --git a/commons-jcs-jcache-openjpa/src/main/java/org/apache/commons/jcs/jcache/openjpa/OpenJPAJCacheDataCacheManager.java b/commons-jcs-jcache-openjpa/src/main/java/org/apache/commons/jcs/jcache/openjpa/OpenJPAJCacheDataCacheManager.java
deleted file mode 100644
index cc9fc85..0000000
--- a/commons-jcs-jcache-openjpa/src/main/java/org/apache/commons/jcs/jcache/openjpa/OpenJPAJCacheDataCacheManager.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.openjpa;
-
-import org.apache.openjpa.conf.OpenJPAConfiguration;
-import org.apache.openjpa.datacache.DataCacheManagerImpl;
-import org.apache.openjpa.lib.conf.ObjectValue;
-
-import javax.cache.Cache;
-import javax.cache.CacheManager;
-import javax.cache.Caching;
-import javax.cache.configuration.FactoryBuilder;
-import javax.cache.configuration.MutableConfiguration;
-import javax.cache.expiry.CreatedExpiryPolicy;
-import javax.cache.expiry.Duration;
-import javax.cache.expiry.ExpiryPolicy;
-import javax.cache.integration.CacheLoader;
-import javax.cache.integration.CacheWriter;
-import javax.cache.spi.CachingProvider;
-import java.net.URI;
-import java.util.Map;
-import java.util.Properties;
-
-public class OpenJPAJCacheDataCacheManager extends DataCacheManagerImpl
-{
-    private CachingProvider provider;
-    private CacheManager cacheManager;
-
-    @Override
-    public void initialize(final OpenJPAConfiguration conf, final ObjectValue dataCache, final ObjectValue queryCache)
-    {
-        super.initialize(conf, dataCache, queryCache);
-        provider = Caching.getCachingProvider();
-
-        final Properties properties = new Properties();
-        final Map<String, Object> props = conf.toProperties(false);
-        if (props != null)
-        {
-            for (final Map.Entry<?, ?> entry : props.entrySet())
-            {
-                if (entry.getKey() != null && entry.getValue() != null)
-                {
-                    properties.setProperty(entry.getKey().toString(), entry.getValue().toString());
-                }
-            }
-        }
-
-        final String uri = properties.getProperty("jcache.uri", provider.getDefaultURI().toString());
-        cacheManager = provider.getCacheManager(URI.create(uri), provider.getDefaultClassLoader(), properties);
-    }
-
-    @Override
-    public void close()
-    {
-        super.close();
-        if (!cacheManager.isClosed())
-        {
-            cacheManager.close();
-        }
-        provider.close();
-    }
-
-    Cache<Object, Object> getOrCreateCache(final String prefix, final String entity)
-    {
-        final String internalName = prefix + entity;
-        Cache<Object, Object> cache = cacheManager.getCache(internalName);
-        if (cache == null)
-        {
-            final Properties properties = cacheManager.getProperties();
-            final MutableConfiguration<Object, Object> configuration = new MutableConfiguration<Object, Object>()
-                    .setStoreByValue("true".equalsIgnoreCase(properties.getProperty("jcache.store-by-value", "false")));
-
-            configuration.setReadThrough("true".equals(properties.getProperty("jcache.read-through", "false")));
-            configuration.setWriteThrough("true".equals(properties.getProperty("jcache.write-through", "false")));
-            if (configuration.isReadThrough())
-            {
-                configuration.setCacheLoaderFactory(new FactoryBuilder.ClassFactory<CacheLoader<Object, Object>>(properties.getProperty("jcache.cache-loader-factory")));
-            }
-            if (configuration.isWriteThrough())
-            {
-                configuration.setCacheWriterFactory(new FactoryBuilder.ClassFactory<CacheWriter<Object, Object>>(properties.getProperty("jcache.cache-writer-factory")));
-            }
-            final String expirtyPolicy = properties.getProperty("jcache.expiry-policy-factory");
-            if (expirtyPolicy != null)
-            {
-                configuration.setExpiryPolicyFactory(new FactoryBuilder.ClassFactory<ExpiryPolicy>(expirtyPolicy));
-            }
-            else
-            {
-                configuration.setExpiryPolicyFactory(new FactoryBuilder.SingletonFactory<ExpiryPolicy>(new CreatedExpiryPolicy(Duration.FIVE_MINUTES)));
-            }
-            configuration.setManagementEnabled("true".equals(properties.getProperty("jcache.management-enabled", "false")));
-            configuration.setStatisticsEnabled("true".equals(properties.getProperty("jcache.statistics-enabled", "false")));
-
-            cache = cacheManager.createCache(internalName, configuration);
-        }
-        return cache;
-    }
-
-    CacheManager getCacheManager()
-    {
-        return cacheManager;
-    }
-}
diff --git a/commons-jcs-jcache-openjpa/src/main/java/org/apache/commons/jcs/jcache/openjpa/OpenJPAJCacheDerivation.java b/commons-jcs-jcache-openjpa/src/main/java/org/apache/commons/jcs/jcache/openjpa/OpenJPAJCacheDerivation.java
deleted file mode 100644
index 4990bb0..0000000
--- a/commons-jcs-jcache-openjpa/src/main/java/org/apache/commons/jcs/jcache/openjpa/OpenJPAJCacheDerivation.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.openjpa;
-
-import org.apache.openjpa.conf.OpenJPAConfiguration;
-import org.apache.openjpa.conf.OpenJPAConfigurationImpl;
-import org.apache.openjpa.event.SingleJVMRemoteCommitProvider;
-import org.apache.openjpa.lib.conf.AbstractProductDerivation;
-import org.apache.openjpa.lib.conf.Configuration;
-import org.apache.openjpa.lib.conf.ConfigurationProvider;
-import org.apache.openjpa.lib.conf.Configurations;
-
-import java.util.Map;
-
-public class OpenJPAJCacheDerivation extends AbstractProductDerivation
-{
-    private static final String JCACHE_NAME = "jcache";
-
-    @Override
-    public boolean beforeConfigurationLoad(final Configuration conf)
-    {
-        if (OpenJPAConfiguration.class.isInstance(conf)) {
-            final OpenJPAConfigurationImpl oconf = OpenJPAConfigurationImpl.class.cast(conf);
-            oconf.dataCacheManagerPlugin.setAlias(JCACHE_NAME, OpenJPAJCacheDataCacheManager.class.getName());
-            oconf.dataCachePlugin.setAlias(JCACHE_NAME, OpenJPAJCacheDataCache.class.getName());
-            oconf.queryCachePlugin.setAlias(JCACHE_NAME, OpenJPAJCacheQueryCache.class.getName());
-        }
-        return super.beforeConfigurationLoad(conf);
-    }
-
-    @Override
-    public boolean beforeConfigurationConstruct(final ConfigurationProvider cp)
-    {
-        final Map<?, ?> props = cp.getProperties();
-        final Object dcm = Configurations.getProperty("DataCacheManager", props);
-        if (dcm != null && JCACHE_NAME.equals(dcm))
-        {
-            if (Configurations.getProperty("DataCache", props) == null)
-            {
-                cp.addProperty("openjpa.DataCache", JCACHE_NAME);
-            }
-            if (Configurations.getProperty("QueryCache", props) == null)
-            {
-                cp.addProperty("openjpa.QueryCache", JCACHE_NAME);
-            }
-            if (Configurations.getProperty("RemoteCommitProvider", props) == null)
-            {
-                cp.addProperty("openjpa.RemoteCommitProvider", SingleJVMRemoteCommitProvider.class.getName());
-            }
-        }
-        return super.beforeConfigurationConstruct(cp);
-    }
-
-    @Override
-    public int getType()
-    {
-        return TYPE_FEATURE;
-    }
-}
diff --git a/commons-jcs-jcache-openjpa/src/main/java/org/apache/commons/jcs/jcache/openjpa/OpenJPAJCacheQueryCache.java b/commons-jcs-jcache-openjpa/src/main/java/org/apache/commons/jcs/jcache/openjpa/OpenJPAJCacheQueryCache.java
deleted file mode 100644
index 7ac021a..0000000
--- a/commons-jcs-jcache-openjpa/src/main/java/org/apache/commons/jcs/jcache/openjpa/OpenJPAJCacheQueryCache.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.openjpa;
-
-import org.apache.openjpa.datacache.AbstractQueryCache;
-import org.apache.openjpa.datacache.DataCacheManager;
-import org.apache.openjpa.datacache.QueryKey;
-import org.apache.openjpa.datacache.QueryResult;
-
-import javax.cache.Cache;
-import javax.cache.CacheManager;
-import java.util.Collection;
-import java.util.LinkedList;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-public class OpenJPAJCacheQueryCache extends AbstractQueryCache
-{
-    private static final String OPENJPA_PREFIX = "openjpa.querycache.";
-    private static final String QUERY_CACHE_NAME = "query";
-
-    private final Lock lock = new ReentrantLock();
-    private OpenJPAJCacheDataCacheManager manager;
-
-    @Override
-    public void initialize(final DataCacheManager manager)
-    {
-        super.initialize(manager);
-        this.manager = OpenJPAJCacheDataCacheManager.class.cast(manager);
-    }
-
-    @Override
-    protected void clearInternal()
-    {
-        final CacheManager cacheManager = manager.getCacheManager();
-        for (final String cacheName : cacheManager.getCacheNames())
-        {
-            if (!cacheName.startsWith(OPENJPA_PREFIX))
-            {
-                continue;
-            }
-            cacheManager.getCache(cacheName).clear();
-        }
-    }
-
-    @Override
-    protected Collection keySet()
-    {
-        final Collection<QueryKey> keys = new LinkedList<QueryKey>();
-        for (final Cache.Entry<Object, Object> entry : queryCache())
-        {
-            keys.add(QueryKey.class.cast(entry.getKey()));
-        }
-        return keys;
-    }
-
-    @Override
-    protected QueryResult getInternal(final QueryKey qk)
-    {
-        return QueryResult.class.cast(queryCache().get(qk));
-    }
-
-    private Cache<Object, Object> queryCache()
-    {
-        return manager.getOrCreateCache(OPENJPA_PREFIX, QUERY_CACHE_NAME);
-    }
-
-    @Override
-    protected QueryResult putInternal(final QueryKey qk, final QueryResult oids)
-    {
-        queryCache().put(qk, oids);
-        return oids;
-    }
-
-    @Override
-    protected QueryResult removeInternal(final QueryKey qk)
-    {
-        final Object remove = queryCache().getAndRemove(qk);
-        if (remove == null)
-        {
-            return null;
-        }
-        return QueryResult.class.cast(remove);
-    }
-
-    @Override
-    protected boolean pinInternal(final QueryKey qk)
-    {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    protected boolean unpinInternal(final QueryKey qk)
-    {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void writeLock()
-    {
-        lock.lock();
-    }
-
-    @Override
-    public void writeUnlock()
-    {
-        lock.unlock();
-    }
-}
diff --git a/commons-jcs-jcache-openjpa/src/main/java/org/apache/commons/jcs3/jcache/openjpa/OpenJPAJCacheDataCache.java b/commons-jcs-jcache-openjpa/src/main/java/org/apache/commons/jcs3/jcache/openjpa/OpenJPAJCacheDataCache.java
new file mode 100644
index 0000000..8025b36
--- /dev/null
+++ b/commons-jcs-jcache-openjpa/src/main/java/org/apache/commons/jcs3/jcache/openjpa/OpenJPAJCacheDataCache.java
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.openjpa;
+
+import org.apache.openjpa.datacache.AbstractDataCache;
+import org.apache.openjpa.datacache.DataCacheManager;
+import org.apache.openjpa.datacache.DataCachePCData;
+import org.apache.openjpa.util.OpenJPAId;
+
+import javax.cache.Cache;
+import javax.cache.CacheManager;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class OpenJPAJCacheDataCache extends AbstractDataCache
+{
+    private static final String OPENJPA_PREFIX = "openjpa.datacache.";
+
+    private final Lock lock = new ReentrantLock();
+    private OpenJPAJCacheDataCacheManager manager;
+
+    @Override
+    public void initialize(final DataCacheManager manager)
+    {
+        super.initialize(manager);
+        this.manager = OpenJPAJCacheDataCacheManager.class.cast(manager);
+    }
+
+    @Override
+    protected DataCachePCData getInternal(final Object oid)
+    {
+        Object result = null;
+        if (OpenJPAId.class.isInstance(oid))
+        {
+            final Class<?> cls = OpenJPAId.class.cast(oid).getType();
+            Cache<Object, Object> cache = manager.getOrCreateCache(OPENJPA_PREFIX, cls.getName());
+            if (cache == null)
+            {
+                return null;
+            }
+            else
+            {
+                result = cache.get(oid);
+            }
+        }
+        else
+        {
+            final CacheManager cacheManager = manager.getCacheManager();
+            for (final String cacheName : cacheManager.getCacheNames())
+            {
+                if (!cacheName.startsWith(OPENJPA_PREFIX))
+                {
+                    continue;
+                }
+
+                result = cacheManager.getCache(cacheName).get(oid);
+                if (result != null)
+                {
+                    break;
+                }
+            }
+        }
+        if (result == null)
+        {
+            return null;
+        }
+        return DataCachePCData.class.cast(result);
+    }
+
+    @Override
+    protected DataCachePCData putInternal(final Object oid, final DataCachePCData pc)
+    {
+        manager.getOrCreateCache(OPENJPA_PREFIX, pc.getType().getName()).put(oid, pc);
+        return pc;
+    }
+
+    @Override
+    protected DataCachePCData removeInternal(final Object oid)
+    {
+        if (OpenJPAId.class.isInstance(oid))
+        {
+            final Object remove = manager.getOrCreateCache(OPENJPA_PREFIX, OpenJPAId.class.cast(oid).getType().getName()).getAndRemove(oid);
+            if (remove == null)
+            {
+                return null;
+            }
+            return DataCachePCData.class.cast(remove);
+        }
+        return null;
+    }
+
+    @Override
+    protected void removeAllInternal(final Class<?> cls, final boolean subclasses)
+    {
+        final String name;
+        if (subclasses)
+        {
+            name = cls.getSuperclass().getName();
+        }
+        else
+        {
+            name = cls.getName();
+        }
+        manager.getOrCreateCache(OPENJPA_PREFIX, name).removeAll();
+    }
+
+    @Override
+    protected void clearInternal()
+    {
+        final CacheManager cacheManager = manager.getCacheManager();
+        for (final String cacheName : cacheManager.getCacheNames())
+        {
+            if (!cacheName.startsWith(OPENJPA_PREFIX))
+            {
+                continue;
+            }
+            cacheManager.getCache(cacheName).clear();
+        }
+    }
+
+    @Override
+    protected boolean pinInternal(final Object oid)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    protected boolean unpinInternal(final Object oid)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void writeLock()
+    {
+        lock.lock();
+    }
+
+    @Override
+    public void writeUnlock()
+    {
+        lock.unlock();
+    }
+}
diff --git a/commons-jcs-jcache-openjpa/src/main/java/org/apache/commons/jcs3/jcache/openjpa/OpenJPAJCacheDataCacheManager.java b/commons-jcs-jcache-openjpa/src/main/java/org/apache/commons/jcs3/jcache/openjpa/OpenJPAJCacheDataCacheManager.java
new file mode 100644
index 0000000..50bbdaa
--- /dev/null
+++ b/commons-jcs-jcache-openjpa/src/main/java/org/apache/commons/jcs3/jcache/openjpa/OpenJPAJCacheDataCacheManager.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.openjpa;
+
+import org.apache.openjpa.conf.OpenJPAConfiguration;
+import org.apache.openjpa.datacache.DataCacheManagerImpl;
+import org.apache.openjpa.lib.conf.ObjectValue;
+
+import javax.cache.Cache;
+import javax.cache.CacheManager;
+import javax.cache.Caching;
+import javax.cache.configuration.FactoryBuilder;
+import javax.cache.configuration.MutableConfiguration;
+import javax.cache.expiry.CreatedExpiryPolicy;
+import javax.cache.expiry.Duration;
+import javax.cache.expiry.ExpiryPolicy;
+import javax.cache.integration.CacheLoader;
+import javax.cache.integration.CacheWriter;
+import javax.cache.spi.CachingProvider;
+import java.net.URI;
+import java.util.Map;
+import java.util.Properties;
+
+public class OpenJPAJCacheDataCacheManager extends DataCacheManagerImpl
+{
+    private CachingProvider provider;
+    private CacheManager cacheManager;
+
+    @Override
+    public void initialize(final OpenJPAConfiguration conf, final ObjectValue dataCache, final ObjectValue queryCache)
+    {
+        super.initialize(conf, dataCache, queryCache);
+        provider = Caching.getCachingProvider();
+
+        final Properties properties = new Properties();
+        final Map<String, Object> props = conf.toProperties(false);
+        if (props != null)
+        {
+            for (final Map.Entry<?, ?> entry : props.entrySet())
+            {
+                if (entry.getKey() != null && entry.getValue() != null)
+                {
+                    properties.setProperty(entry.getKey().toString(), entry.getValue().toString());
+                }
+            }
+        }
+
+        final String uri = properties.getProperty("jcache.uri", provider.getDefaultURI().toString());
+        cacheManager = provider.getCacheManager(URI.create(uri), provider.getDefaultClassLoader(), properties);
+    }
+
+    @Override
+    public void close()
+    {
+        super.close();
+        if (!cacheManager.isClosed())
+        {
+            cacheManager.close();
+        }
+        provider.close();
+    }
+
+    Cache<Object, Object> getOrCreateCache(final String prefix, final String entity)
+    {
+        final String internalName = prefix + entity;
+        Cache<Object, Object> cache = cacheManager.getCache(internalName);
+        if (cache == null)
+        {
+            final Properties properties = cacheManager.getProperties();
+            final MutableConfiguration<Object, Object> configuration = new MutableConfiguration<Object, Object>()
+                    .setStoreByValue("true".equalsIgnoreCase(properties.getProperty("jcache.store-by-value", "false")));
+
+            configuration.setReadThrough("true".equals(properties.getProperty("jcache.read-through", "false")));
+            configuration.setWriteThrough("true".equals(properties.getProperty("jcache.write-through", "false")));
+            if (configuration.isReadThrough())
+            {
+                configuration.setCacheLoaderFactory(new FactoryBuilder.ClassFactory<CacheLoader<Object, Object>>(properties.getProperty("jcache.cache-loader-factory")));
+            }
+            if (configuration.isWriteThrough())
+            {
+                configuration.setCacheWriterFactory(new FactoryBuilder.ClassFactory<CacheWriter<Object, Object>>(properties.getProperty("jcache.cache-writer-factory")));
+            }
+            final String expirtyPolicy = properties.getProperty("jcache.expiry-policy-factory");
+            if (expirtyPolicy != null)
+            {
+                configuration.setExpiryPolicyFactory(new FactoryBuilder.ClassFactory<ExpiryPolicy>(expirtyPolicy));
+            }
+            else
+            {
+                configuration.setExpiryPolicyFactory(new FactoryBuilder.SingletonFactory<ExpiryPolicy>(new CreatedExpiryPolicy(Duration.FIVE_MINUTES)));
+            }
+            configuration.setManagementEnabled("true".equals(properties.getProperty("jcache.management-enabled", "false")));
+            configuration.setStatisticsEnabled("true".equals(properties.getProperty("jcache.statistics-enabled", "false")));
+
+            cache = cacheManager.createCache(internalName, configuration);
+        }
+        return cache;
+    }
+
+    CacheManager getCacheManager()
+    {
+        return cacheManager;
+    }
+}
diff --git a/commons-jcs-jcache-openjpa/src/main/java/org/apache/commons/jcs3/jcache/openjpa/OpenJPAJCacheDerivation.java b/commons-jcs-jcache-openjpa/src/main/java/org/apache/commons/jcs3/jcache/openjpa/OpenJPAJCacheDerivation.java
new file mode 100644
index 0000000..97575a4
--- /dev/null
+++ b/commons-jcs-jcache-openjpa/src/main/java/org/apache/commons/jcs3/jcache/openjpa/OpenJPAJCacheDerivation.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.openjpa;
+
+import org.apache.openjpa.conf.OpenJPAConfiguration;
+import org.apache.openjpa.conf.OpenJPAConfigurationImpl;
+import org.apache.openjpa.event.SingleJVMRemoteCommitProvider;
+import org.apache.openjpa.lib.conf.AbstractProductDerivation;
+import org.apache.openjpa.lib.conf.Configuration;
+import org.apache.openjpa.lib.conf.ConfigurationProvider;
+import org.apache.openjpa.lib.conf.Configurations;
+
+import java.util.Map;
+
+public class OpenJPAJCacheDerivation extends AbstractProductDerivation
+{
+    private static final String JCACHE_NAME = "jcache";
+
+    @Override
+    public boolean beforeConfigurationLoad(final Configuration conf)
+    {
+        if (OpenJPAConfiguration.class.isInstance(conf)) {
+            final OpenJPAConfigurationImpl oconf = OpenJPAConfigurationImpl.class.cast(conf);
+            oconf.dataCacheManagerPlugin.setAlias(JCACHE_NAME, OpenJPAJCacheDataCacheManager.class.getName());
+            oconf.dataCachePlugin.setAlias(JCACHE_NAME, OpenJPAJCacheDataCache.class.getName());
+            oconf.queryCachePlugin.setAlias(JCACHE_NAME, OpenJPAJCacheQueryCache.class.getName());
+        }
+        return super.beforeConfigurationLoad(conf);
+    }
+
+    @Override
+    public boolean beforeConfigurationConstruct(final ConfigurationProvider cp)
+    {
+        final Map<?, ?> props = cp.getProperties();
+        final Object dcm = Configurations.getProperty("DataCacheManager", props);
+        if (dcm != null && JCACHE_NAME.equals(dcm))
+        {
+            if (Configurations.getProperty("DataCache", props) == null)
+            {
+                cp.addProperty("openjpa.DataCache", JCACHE_NAME);
+            }
+            if (Configurations.getProperty("QueryCache", props) == null)
+            {
+                cp.addProperty("openjpa.QueryCache", JCACHE_NAME);
+            }
+            if (Configurations.getProperty("RemoteCommitProvider", props) == null)
+            {
+                cp.addProperty("openjpa.RemoteCommitProvider", SingleJVMRemoteCommitProvider.class.getName());
+            }
+        }
+        return super.beforeConfigurationConstruct(cp);
+    }
+
+    @Override
+    public int getType()
+    {
+        return TYPE_FEATURE;
+    }
+}
diff --git a/commons-jcs-jcache-openjpa/src/main/java/org/apache/commons/jcs3/jcache/openjpa/OpenJPAJCacheQueryCache.java b/commons-jcs-jcache-openjpa/src/main/java/org/apache/commons/jcs3/jcache/openjpa/OpenJPAJCacheQueryCache.java
new file mode 100644
index 0000000..27818a3
--- /dev/null
+++ b/commons-jcs-jcache-openjpa/src/main/java/org/apache/commons/jcs3/jcache/openjpa/OpenJPAJCacheQueryCache.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.openjpa;
+
+import org.apache.openjpa.datacache.AbstractQueryCache;
+import org.apache.openjpa.datacache.DataCacheManager;
+import org.apache.openjpa.datacache.QueryKey;
+import org.apache.openjpa.datacache.QueryResult;
+
+import javax.cache.Cache;
+import javax.cache.CacheManager;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class OpenJPAJCacheQueryCache extends AbstractQueryCache
+{
+    private static final String OPENJPA_PREFIX = "openjpa.querycache.";
+    private static final String QUERY_CACHE_NAME = "query";
+
+    private final Lock lock = new ReentrantLock();
+    private OpenJPAJCacheDataCacheManager manager;
+
+    @Override
+    public void initialize(final DataCacheManager manager)
+    {
+        super.initialize(manager);
+        this.manager = OpenJPAJCacheDataCacheManager.class.cast(manager);
+    }
+
+    @Override
+    protected void clearInternal()
+    {
+        final CacheManager cacheManager = manager.getCacheManager();
+        for (final String cacheName : cacheManager.getCacheNames())
+        {
+            if (!cacheName.startsWith(OPENJPA_PREFIX))
+            {
+                continue;
+            }
+            cacheManager.getCache(cacheName).clear();
+        }
+    }
+
+    @Override
+    protected Collection keySet()
+    {
+        final Collection<QueryKey> keys = new LinkedList<QueryKey>();
+        for (final Cache.Entry<Object, Object> entry : queryCache())
+        {
+            keys.add(QueryKey.class.cast(entry.getKey()));
+        }
+        return keys;
+    }
+
+    @Override
+    protected QueryResult getInternal(final QueryKey qk)
+    {
+        return QueryResult.class.cast(queryCache().get(qk));
+    }
+
+    private Cache<Object, Object> queryCache()
+    {
+        return manager.getOrCreateCache(OPENJPA_PREFIX, QUERY_CACHE_NAME);
+    }
+
+    @Override
+    protected QueryResult putInternal(final QueryKey qk, final QueryResult oids)
+    {
+        queryCache().put(qk, oids);
+        return oids;
+    }
+
+    @Override
+    protected QueryResult removeInternal(final QueryKey qk)
+    {
+        final Object remove = queryCache().getAndRemove(qk);
+        if (remove == null)
+        {
+            return null;
+        }
+        return QueryResult.class.cast(remove);
+    }
+
+    @Override
+    protected boolean pinInternal(final QueryKey qk)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    protected boolean unpinInternal(final QueryKey qk)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void writeLock()
+    {
+        lock.lock();
+    }
+
+    @Override
+    public void writeUnlock()
+    {
+        lock.unlock();
+    }
+}
diff --git a/commons-jcs-jcache-openjpa/src/main/resources/META-INF/services/org.apache.openjpa.lib.conf.ProductDerivation b/commons-jcs-jcache-openjpa/src/main/resources/META-INF/services/org.apache.openjpa.lib.conf.ProductDerivation
index 092e0cc..f39722f 100644
--- a/commons-jcs-jcache-openjpa/src/main/resources/META-INF/services/org.apache.openjpa.lib.conf.ProductDerivation
+++ b/commons-jcs-jcache-openjpa/src/main/resources/META-INF/services/org.apache.openjpa.lib.conf.ProductDerivation
@@ -1 +1,18 @@
-org.apache.commons.jcs.jcache.openjpa.OpenJPAJCacheDerivation
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+org.apache.commons.jcs3.jcache.openjpa.OpenJPAJCacheDerivation
diff --git a/commons-jcs-jcache-openjpa/src/test/java/org/apache/commons/jcs/jcache/openjpa/OpenJPAJCacheDataCacheTest.java b/commons-jcs-jcache-openjpa/src/test/java/org/apache/commons/jcs/jcache/openjpa/OpenJPAJCacheDataCacheTest.java
deleted file mode 100644
index 28eba88..0000000
--- a/commons-jcs-jcache-openjpa/src/test/java/org/apache/commons/jcs/jcache/openjpa/OpenJPAJCacheDataCacheTest.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.openjpa;
-
-import org.apache.derby.jdbc.EmbeddedDriver;
-import org.apache.openjpa.conf.OpenJPAConfiguration;
-import org.apache.openjpa.datacache.QueryKey;
-import org.apache.openjpa.persistence.JPAFacadeHelper;
-import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI;
-import org.junit.Test;
-
-import javax.persistence.Entity;
-import javax.persistence.EntityManager;
-import javax.persistence.EntityManagerFactory;
-import javax.persistence.GeneratedValue;
-import javax.persistence.Id;
-import javax.persistence.Persistence;
-import javax.persistence.Query;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Properties;
-
-import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-
-public class OpenJPAJCacheDataCacheTest
-{
-    private static final Properties props = new Properties()
-    {{
-        setProperty("openjpa.MetaDataFactory", "jpa(Types=" + MyEntity.class.getName() + ")");
-        setProperty("openjpa.ConnectionDriverName", EmbeddedDriver.class.getName());
-        setProperty("openjpa.ConnectionURL", "jdbc:derby:memory:test;create=true");
-        setProperty("openjpa.jdbc.SynchronizeMappings", "buildSchema");
-        setProperty("openjpa.DataCacheManager", "jcache");
-        setProperty("openjpa.RuntimeUnenhancedClasses", "supported");
-
-        // implicit
-        // setProperty("openjpa.DataCache", "jcache");
-        // setProperty("openjpa.QueryCache", "jcache");
-    }};
-
-    @Test
-    public void entity()
-    {
-        final EntityManagerFactory emf = Persistence.createEntityManagerFactory("test-jcache", props);
-        final OpenJPAConfiguration conf = OpenJPAEntityManagerFactorySPI.class.cast(emf).getConfiguration();
-
-        final EntityManager em = emf.createEntityManager();
-
-        final MyEntity entity = new MyEntity();
-        entity.setName("cacheMe1");
-        em.getTransaction().begin();
-        em.persist(entity);
-        em.getTransaction().commit();
-        assertNotNull(conf.getDataCacheManagerInstance().getDataCache("default"));
-
-        assertThat(conf.getDataCacheManagerInstance(), instanceOf(OpenJPAJCacheDataCacheManager.class));
-        assertThat(conf.getDataCacheManagerInstance().getDataCache("default"), instanceOf(OpenJPAJCacheDataCache.class));
-        assertTrue(conf.getDataCacheManagerInstance().getDataCache("default").contains(JPAFacadeHelper.toOpenJPAObjectId(conf.getMetaDataRepositoryInstance().getCachedMetaData(MyEntity.class), entity.getId())));
-
-        em.close();
-
-        emf.close();
-    }
-
-    @Test
-    public void query()
-    {
-        final EntityManagerFactory emf = Persistence.createEntityManagerFactory("test-jcache", props);
-        final OpenJPAConfiguration conf = OpenJPAEntityManagerFactorySPI.class.cast(emf).getConfiguration();
-
-        final EntityManager em = emf.createEntityManager();
-
-        final MyEntity entity = new MyEntity();
-        entity.setName("cacheMe1");
-        em.getTransaction().begin();
-        em.persist(entity);
-        em.getTransaction().commit();
-        final Query query = em.createQuery("select e from OpenJPAJCacheDataCacheTest$MyEntity e where e.id = :id");
-        assertEquals(1, query.setParameter("id", entity.getId()).getResultList().size());
-        assertNotNull(conf.getDataCacheManagerInstance().getDataCache("default"));
-
-        assertThat(conf.getDataCacheManagerInstance(), instanceOf(OpenJPAJCacheDataCacheManager.class));
-        assertThat(conf.getDataCacheManagerInstance().getDataCache("default"), instanceOf(OpenJPAJCacheDataCache.class));
-        assertTrue(conf.getDataCacheManagerInstance().getDataCache("default").contains(JPAFacadeHelper.toOpenJPAObjectId(conf.getMetaDataRepositoryInstance().getCachedMetaData(MyEntity.class), entity.getId())));
-
-        final Map<Object, Object> args = new HashMap<Object, Object>()
-        {{
-                put("id", entity.getId());
-        }};
-        final QueryKey qk = QueryKey.newInstance(query.unwrap(org.apache.openjpa.kernel.Query.class), args);
-        assertNotNull(conf.getDataCacheManagerInstance().getSystemQueryCache().get(qk));
-
-        em.close();
-
-        emf.close();
-    }
-
-    @Entity
-    public static class MyEntity
-    {
-        @Id
-        @GeneratedValue
-        private long id;
-        private String name;
-
-        public long getId()
-        {
-            return id;
-        }
-
-        public String getName()
-        {
-            return name;
-        }
-
-        public void setName(final String name)
-        {
-            this.name = name;
-        }
-    }
-}
diff --git a/commons-jcs-jcache-openjpa/src/test/java/org/apache/commons/jcs3/jcache/openjpa/OpenJPAJCacheDataCacheTest.java b/commons-jcs-jcache-openjpa/src/test/java/org/apache/commons/jcs3/jcache/openjpa/OpenJPAJCacheDataCacheTest.java
new file mode 100644
index 0000000..21a447e
--- /dev/null
+++ b/commons-jcs-jcache-openjpa/src/test/java/org/apache/commons/jcs3/jcache/openjpa/OpenJPAJCacheDataCacheTest.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.openjpa;
+
+import org.apache.commons.jcs3.jcache.openjpa.OpenJPAJCacheDataCache;
+import org.apache.commons.jcs3.jcache.openjpa.OpenJPAJCacheDataCacheManager;
+import org.apache.derby.jdbc.EmbeddedDriver;
+import org.apache.openjpa.conf.OpenJPAConfiguration;
+import org.apache.openjpa.datacache.QueryKey;
+import org.apache.openjpa.persistence.JPAFacadeHelper;
+import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI;
+import org.junit.Test;
+
+import javax.persistence.Entity;
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.Persistence;
+import javax.persistence.Query;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+public class OpenJPAJCacheDataCacheTest
+{
+    private static final Properties props = new Properties()
+    {{
+        setProperty("openjpa.MetaDataFactory", "jpa(Types=" + MyEntity.class.getName() + ")");
+        setProperty("openjpa.ConnectionDriverName", EmbeddedDriver.class.getName());
+        setProperty("openjpa.ConnectionURL", "jdbc:derby:memory:test;create=true");
+        setProperty("openjpa.jdbc.SynchronizeMappings", "buildSchema");
+        setProperty("openjpa.DataCacheManager", "jcache");
+        setProperty("openjpa.RuntimeUnenhancedClasses", "supported");
+
+        // implicit
+        // setProperty("openjpa.DataCache", "jcache");
+        // setProperty("openjpa.QueryCache", "jcache");
+    }};
+
+    @Test
+    public void entity()
+    {
+        final EntityManagerFactory emf = Persistence.createEntityManagerFactory("test-jcache", props);
+        final OpenJPAConfiguration conf = OpenJPAEntityManagerFactorySPI.class.cast(emf).getConfiguration();
+
+        final EntityManager em = emf.createEntityManager();
+
+        final MyEntity entity = new MyEntity();
+        entity.setName("cacheMe1");
+        em.getTransaction().begin();
+        em.persist(entity);
+        em.getTransaction().commit();
+        assertNotNull(conf.getDataCacheManagerInstance().getDataCache("default"));
+
+        assertThat(conf.getDataCacheManagerInstance(), instanceOf(OpenJPAJCacheDataCacheManager.class));
+        assertThat(conf.getDataCacheManagerInstance().getDataCache("default"), instanceOf(OpenJPAJCacheDataCache.class));
+        assertTrue(conf.getDataCacheManagerInstance().getDataCache("default").contains(JPAFacadeHelper.toOpenJPAObjectId(conf.getMetaDataRepositoryInstance().getCachedMetaData(MyEntity.class), entity.getId())));
+
+        em.close();
+
+        emf.close();
+    }
+
+    @Test
+    public void query()
+    {
+        final EntityManagerFactory emf = Persistence.createEntityManagerFactory("test-jcache", props);
+        final OpenJPAConfiguration conf = OpenJPAEntityManagerFactorySPI.class.cast(emf).getConfiguration();
+
+        final EntityManager em = emf.createEntityManager();
+
+        final MyEntity entity = new MyEntity();
+        entity.setName("cacheMe1");
+        em.getTransaction().begin();
+        em.persist(entity);
+        em.getTransaction().commit();
+        final Query query = em.createQuery("select e from OpenJPAJCacheDataCacheTest$MyEntity e where e.id = :id");
+        assertEquals(1, query.setParameter("id", entity.getId()).getResultList().size());
+        assertNotNull(conf.getDataCacheManagerInstance().getDataCache("default"));
+
+        assertThat(conf.getDataCacheManagerInstance(), instanceOf(OpenJPAJCacheDataCacheManager.class));
+        assertThat(conf.getDataCacheManagerInstance().getDataCache("default"), instanceOf(OpenJPAJCacheDataCache.class));
+        assertTrue(conf.getDataCacheManagerInstance().getDataCache("default").contains(JPAFacadeHelper.toOpenJPAObjectId(conf.getMetaDataRepositoryInstance().getCachedMetaData(MyEntity.class), entity.getId())));
+
+        final Map<Object, Object> args = new HashMap<Object, Object>()
+        {{
+                put("id", entity.getId());
+        }};
+        final QueryKey qk = QueryKey.newInstance(query.unwrap(org.apache.openjpa.kernel.Query.class), args);
+        assertNotNull(conf.getDataCacheManagerInstance().getSystemQueryCache().get(qk));
+
+        em.close();
+
+        emf.close();
+    }
+
+    @Entity
+    public static class MyEntity
+    {
+        @Id
+        @GeneratedValue
+        private long id;
+        private String name;
+
+        public long getId()
+        {
+            return id;
+        }
+
+        public String getName()
+        {
+            return name;
+        }
+
+        public void setName(final String name)
+        {
+            this.name = name;
+        }
+    }
+}
diff --git a/commons-jcs-jcache/pom.xml b/commons-jcs-jcache/pom.xml
index 9e4eb46..555c59a 100644
--- a/commons-jcs-jcache/pom.xml
+++ b/commons-jcs-jcache/pom.xml
@@ -22,12 +22,12 @@
 
   <parent>
     <groupId>org.apache.commons</groupId>
-    <artifactId>commons-jcs</artifactId>
+    <artifactId>commons-jcs3</artifactId>
     <version>3.0-SNAPSHOT</version>
     <relativePath>../pom.xml</relativePath>
   </parent>
 
-  <artifactId>commons-jcs-jcache</artifactId>
+  <artifactId>commons-jcs3-jcache</artifactId>
   <version>3.0-SNAPSHOT</version>
   <name>Apache Commons JCS :: JCache</name>
 
@@ -61,7 +61,7 @@
 
     <dependency>
       <groupId>org.apache.commons</groupId>
-      <artifactId>commons-jcs-core</artifactId>
+      <artifactId>commons-jcs3-core</artifactId>
     </dependency>
 
     <dependency>
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/Asserts.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/Asserts.java
deleted file mode 100644
index 57d746a..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/Asserts.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package org.apache.commons.jcs.jcache;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-public class Asserts
-{
-    public static void assertNotNull(final Object value, final String name)
-    {
-        if (value == null)
-        {
-            throw new NullPointerException(name + " is null");
-        }
-    }
-
-    private Asserts()
-    {
-        // no-op
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/EvictionListener.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/EvictionListener.java
deleted file mode 100644
index a8ecbc2..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/EvictionListener.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache;
-
-import org.apache.commons.jcs.engine.control.event.behavior.IElementEvent;
-import org.apache.commons.jcs.engine.control.event.behavior.IElementEventHandler;
-
-public class EvictionListener implements IElementEventHandler
-{
-    private final Statistics stats;
-
-    public EvictionListener(final Statistics statistics)
-    {
-        this.stats = statistics;
-    }
-
-    @Override
-    public void handleElementEvent(final IElementEvent event)
-    {
-        switch (event.getElementEvent())
-        {
-            case EXCEEDED_MAXLIFE_BACKGROUND:
-            case EXCEEDED_MAXLIFE_ONREQUEST:
-            case EXCEEDED_IDLETIME_ONREQUEST:
-            case EXCEEDED_IDLETIME_BACKGROUND:
-                stats.increaseEvictions(1);
-                break;
-        }
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/ExpiryAwareCache.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/ExpiryAwareCache.java
deleted file mode 100644
index d4691ec..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/ExpiryAwareCache.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache;
-
-import java.util.Arrays;
-import java.util.Map;
-
-import javax.cache.Cache;
-import javax.cache.configuration.CacheEntryListenerConfiguration;
-import javax.cache.event.CacheEntryEvent;
-import javax.cache.event.EventType;
-
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheAttributes;
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-import org.apache.commons.jcs.engine.control.CompositeCache;
-
-// allows us to plug some lifecycle callbacks on the core cache without impacting too much the core
-public class ExpiryAwareCache<A, B> extends CompositeCache<A, B>
-{
-    private Map<CacheEntryListenerConfiguration<A, B>, JCSListener<A, B>> listeners;
-    private Cache<A, B> cacheRef;
-
-    ExpiryAwareCache(final ICompositeCacheAttributes cattr, final IElementAttributes attr)
-    {
-        super(cattr, attr);
-    }
-
-    @Override
-    protected void doExpires(final ICacheElement<A, B> element)
-    {
-        super.doExpires(element);
-        for (final JCSListener<A, B> listener : listeners.values())
-        {
-            listener.onExpired(Arrays.<CacheEntryEvent<? extends A, ? extends B>> asList(new JCSCacheEntryEvent<A, B>(
-                    cacheRef, EventType.REMOVED, null, element.getKey(), element.getVal())));
-        }
-    }
-
-    void init(final Cache<A, B> cache, final Map<CacheEntryListenerConfiguration<A, B>, JCSListener<A, B>> listeners)
-    {
-        this.cacheRef = cache;
-        this.listeners = listeners;
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/ImmutableIterable.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/ImmutableIterable.java
deleted file mode 100644
index c4390e9..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/ImmutableIterable.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-
-public class ImmutableIterable<T> implements Iterable<T>
-{
-    private final Collection<T> delegate;
-
-    public ImmutableIterable(final Collection<T> delegate)
-    {
-        this.delegate = new ArrayList<T>(delegate);
-    }
-
-    @Override
-    public Iterator<T> iterator()
-    {
-        return new ImmutableIterator<T>(delegate.iterator());
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/ImmutableIterator.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/ImmutableIterator.java
deleted file mode 100644
index 55c9942..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/ImmutableIterator.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache;
-
-import java.util.Iterator;
-
-public class ImmutableIterator<T> implements Iterator<T>
-{
-    private final Iterator<T> delegate;
-
-    public ImmutableIterator(final Iterator<T> delegate)
-    {
-        this.delegate = delegate;
-    }
-
-    @Override
-    public boolean hasNext()
-    {
-        return delegate.hasNext();
-    }
-
-    @Override
-    public T next()
-    {
-        return delegate.next();
-    }
-
-    @Override
-    public void remove()
-    {
-        throw new UnsupportedOperationException("this iterator is immutable");
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/JCSCache.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/JCSCache.java
deleted file mode 100644
index baa1ff5..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/JCSCache.java
+++ /dev/null
@@ -1,1014 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache;
-
-import org.apache.commons.jcs.engine.CacheElement;
-import org.apache.commons.jcs.engine.ElementAttributes;
-import org.apache.commons.jcs.engine.behavior.ICacheElement;
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs.jcache.jmx.JCSCacheMXBean;
-import org.apache.commons.jcs.jcache.jmx.JCSCacheStatisticsMXBean;
-import org.apache.commons.jcs.jcache.jmx.JMXs;
-import org.apache.commons.jcs.jcache.proxy.ExceptionWrapperHandler;
-import org.apache.commons.jcs.jcache.thread.DaemonThreadFactory;
-import org.apache.commons.jcs.utils.serialization.StandardSerializer;
-
-import javax.cache.Cache;
-import javax.cache.CacheException;
-import javax.cache.CacheManager;
-import javax.cache.configuration.CacheEntryListenerConfiguration;
-import javax.cache.configuration.Configuration;
-import javax.cache.configuration.Factory;
-import javax.cache.event.CacheEntryEvent;
-import javax.cache.event.EventType;
-import javax.cache.expiry.Duration;
-import javax.cache.expiry.EternalExpiryPolicy;
-import javax.cache.expiry.ExpiryPolicy;
-import javax.cache.integration.CacheLoader;
-import javax.cache.integration.CacheLoaderException;
-import javax.cache.integration.CacheWriter;
-import javax.cache.integration.CacheWriterException;
-import javax.cache.integration.CompletionListener;
-import javax.cache.processor.EntryProcessor;
-import javax.cache.processor.EntryProcessorException;
-import javax.cache.processor.EntryProcessorResult;
-import javax.management.ObjectName;
-import java.io.Closeable;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-import static org.apache.commons.jcs.jcache.Asserts.assertNotNull;
-import static org.apache.commons.jcs.jcache.serialization.Serializations.copy;
-
-// TODO: configure serializer
-public class JCSCache<K, V> implements Cache<K, V>
-{
-    private final ExpiryAwareCache<K, V> delegate;
-    private final JCSCachingManager manager;
-    private final JCSConfiguration<K, V> config;
-    private final CacheLoader<K, V> loader;
-    private final CacheWriter<? super K, ? super V> writer;
-    private final ExpiryPolicy expiryPolicy;
-    private final ObjectName cacheConfigObjectName;
-    private final ObjectName cacheStatsObjectName;
-    private final String name;
-    private volatile boolean closed = false;
-    private final Map<CacheEntryListenerConfiguration<K, V>, JCSListener<K, V>> listeners = new ConcurrentHashMap<CacheEntryListenerConfiguration<K, V>, JCSListener<K, V>>();
-    private final Statistics statistics = new Statistics();
-    private final ExecutorService pool;
-    private final IElementSerializer serializer; // using json/xml should work as well -> don't force Serializable
-
-
-    public JCSCache(final ClassLoader classLoader, final JCSCachingManager mgr,
-                    final String cacheName, final JCSConfiguration<K, V> configuration,
-                    final Properties properties, final ExpiryAwareCache<K, V> cache)
-    {
-        manager = mgr;
-
-        name = cacheName;
-
-        delegate = cache;
-        if (delegate.getElementAttributes() == null)
-        {
-            delegate.setElementAttributes(new ElementAttributes());
-        }
-        delegate.getElementAttributes().addElementEventHandler(new EvictionListener(statistics));
-
-        config = configuration;
-
-        final int poolSize = Integer.parseInt(property(properties, cacheName, "pool.size", "3"));
-        final DaemonThreadFactory threadFactory = new DaemonThreadFactory("JCS-JCache-" + cacheName + "-");
-        pool = poolSize > 0 ? Executors.newFixedThreadPool(poolSize, threadFactory) : Executors.newCachedThreadPool(threadFactory);
-
-        try
-        {
-            serializer = IElementSerializer.class.cast(classLoader.loadClass(property(properties, "serializer", cacheName, StandardSerializer.class.getName())).newInstance());
-        }
-        catch (final Exception e)
-        {
-            throw new IllegalArgumentException(e);
-        }
-
-        final Factory<CacheLoader<K, V>> cacheLoaderFactory = configuration.getCacheLoaderFactory();
-        if (cacheLoaderFactory == null)
-        {
-            loader = NoLoader.INSTANCE;
-        }
-        else
-        {
-            loader = ExceptionWrapperHandler
-                    .newProxy(classLoader, cacheLoaderFactory.create(), CacheLoaderException.class, CacheLoader.class);
-        }
-
-        final Factory<CacheWriter<? super K, ? super V>> cacheWriterFactory = configuration.getCacheWriterFactory();
-        if (cacheWriterFactory == null)
-        {
-            writer = NoWriter.INSTANCE;
-        }
-        else
-        {
-            writer = ExceptionWrapperHandler
-                    .newProxy(classLoader, cacheWriterFactory.create(), CacheWriterException.class, CacheWriter.class);
-        }
-
-        final Factory<ExpiryPolicy> expiryPolicyFactory = configuration.getExpiryPolicyFactory();
-        if (expiryPolicyFactory == null)
-        {
-            expiryPolicy = new EternalExpiryPolicy();
-        }
-        else
-        {
-            expiryPolicy = expiryPolicyFactory.create();
-        }
-
-        for (final CacheEntryListenerConfiguration<K, V> listener : config.getCacheEntryListenerConfigurations())
-        {
-            listeners.put(listener, new JCSListener<K, V>(listener));
-        }
-        delegate.init(this, listeners);
-
-        statistics.setActive(config.isStatisticsEnabled());
-
-        final String mgrStr = manager.getURI().toString().replaceAll(",|:|=|\n", ".");
-        final String cacheStr = name.replaceAll(",|:|=|\n", ".");
-        try
-        {
-            cacheConfigObjectName = new ObjectName("javax.cache:type=CacheConfiguration,"
-                    + "CacheManager=" + mgrStr + "," + "Cache=" + cacheStr);
-            cacheStatsObjectName = new ObjectName("javax.cache:type=CacheStatistics,"
-                    + "CacheManager=" + mgrStr + "," + "Cache=" + cacheStr);
-        }
-        catch (final Exception e)
-        {
-            throw new IllegalArgumentException(e);
-        }
-        if (config.isManagementEnabled())
-        {
-            JMXs.register(cacheConfigObjectName, new JCSCacheMXBean<K, V>(this));
-        }
-        if (config.isStatisticsEnabled())
-        {
-            JMXs.register(cacheStatsObjectName, new JCSCacheStatisticsMXBean(statistics));
-        }
-    }
-
-    private static String property(final Properties properties, final String cacheName, final String name, final String defaultValue)
-    {
-        return properties.getProperty(cacheName + "." + name, properties.getProperty(name, defaultValue));
-    }
-
-    private void assertNotClosed()
-    {
-        if (isClosed())
-        {
-            throw new IllegalStateException("cache closed");
-        }
-    }
-
-    @Override
-    public V get(final K key)
-    {
-        assertNotClosed();
-        assertNotNull(key, "key");
-        final long getStart = Times.now(false);
-        return doGetControllingExpiry(getStart, key, true, false, false, true);
-    }
-
-    private V doLoad(final K key, final boolean update, final long now, final boolean propagateLoadException)
-    {
-        V v = null;
-        try
-        {
-            v = loader.load(key);
-        }
-        catch (final CacheLoaderException e)
-        {
-            if (propagateLoadException)
-            {
-                throw e;
-            }
-        }
-        if (v != null)
-        {
-            final Duration duration = update ? expiryPolicy.getExpiryForUpdate() : expiryPolicy.getExpiryForCreation();
-            if (isNotZero(duration))
-            {
-                final IElementAttributes clone = delegate.getElementAttributes().clone();
-                if (ElementAttributes.class.isInstance(clone))
-                {
-                    ElementAttributes.class.cast(clone).setCreateTime();
-                }
-                final ICacheElement<K, V> element = updateElement(key, v, duration, clone);
-                try
-                {
-                    delegate.update(element);
-                }
-                catch (final IOException e)
-                {
-                    throw new CacheException(e);
-                }
-            }
-        }
-        return v;
-    }
-
-    private ICacheElement<K, V> updateElement(final K key, final V v, final Duration duration, final IElementAttributes attrs)
-    {
-        final ICacheElement<K, V> element = new CacheElement<K, V>(name, key, v);
-        if (duration != null)
-        {
-            attrs.setTimeFactorForMilliseconds(1);
-            final boolean eternal = duration.isEternal();
-            attrs.setIsEternal(eternal);
-            if (!eternal)
-            {
-                attrs.setLastAccessTimeNow();
-            }
-            // MaxLife = -1 to use IdleTime excepted if jcache.ccf asked for something else
-        }
-        element.setElementAttributes(attrs);
-        return element;
-    }
-
-    private void touch(final K key, final ICacheElement<K, V> element)
-    {
-        if (config.isStoreByValue())
-        {
-            final K copy = copy(serializer, manager.getClassLoader(), key);
-            try
-            {
-                delegate.update(new CacheElement<K, V>(name, copy, element.getVal(), element.getElementAttributes()));
-            }
-            catch (final IOException e)
-            {
-                throw new CacheException(e);
-            }
-        }
-    }
-
-    @Override
-    public Map<K, V> getAll(final Set<? extends K> keys)
-    {
-        assertNotClosed();
-        for (final K k : keys)
-        {
-            assertNotNull(k, "key");
-        }
-
-        final long now = Times.now(false);
-        final Map<K, V> result = new HashMap<K, V>();
-        for (final K key : keys) {
-            assertNotNull(key, "key");
-
-            final ICacheElement<K, V> elt = delegate.get(key);
-            V val = elt != null ? elt.getVal() : null;
-            if (val == null && config.isReadThrough())
-            {
-                val = doLoad(key, false, now, false);
-                if (val != null)
-                {
-                    result.put(key, val);
-                }
-            }
-            else if (elt != null)
-            {
-                final Duration expiryForAccess = expiryPolicy.getExpiryForAccess();
-                if (isNotZero(expiryForAccess))
-                {
-                    touch(key, elt);
-                    result.put(key, val);
-                }
-                else
-                {
-                    forceExpires(key);
-                }
-            }
-        }
-        return result;
-    }
-
-    @Override
-    public boolean containsKey(final K key)
-    {
-        assertNotClosed();
-        assertNotNull(key, "key");
-        return delegate.get(key) != null;
-    }
-
-    @Override
-    public void put(final K key, final V rawValue)
-    {
-        assertNotClosed();
-        assertNotNull(key, "key");
-        assertNotNull(rawValue, "value");
-
-        final ICacheElement<K, V> oldElt = delegate.get(key);
-        final V old = oldElt != null ? oldElt.getVal() : null;
-
-        final boolean storeByValue = config.isStoreByValue();
-        final V value = storeByValue ? copy(serializer, manager.getClassLoader(), rawValue) : rawValue;
-
-        final boolean created = old == null;
-        final Duration duration = created ? expiryPolicy.getExpiryForCreation() : expiryPolicy.getExpiryForUpdate();
-        if (isNotZero(duration))
-        {
-            final boolean statisticsEnabled = config.isStatisticsEnabled();
-            final long start = Times.now(false);
-
-            final K jcsKey = storeByValue ? copy(serializer, manager.getClassLoader(), key) : key;
-            final ICacheElement<K, V> element = updateElement( // reuse it to create basic structure
-                    jcsKey, value, created ? null : duration,
-                    oldElt != null ? oldElt.getElementAttributes() : delegate.getElementAttributes().clone());
-            if (created && duration != null) { // set maxLife
-                final IElementAttributes copy = element.getElementAttributes();
-                copy.setTimeFactorForMilliseconds(1);
-                final boolean eternal = duration.isEternal();
-                copy.setIsEternal(eternal);
-                if (ElementAttributes.class.isInstance(copy)) {
-                    ElementAttributes.class.cast(copy).setCreateTime();
-                }
-                if (!eternal)
-                {
-                    copy.setIsEternal(false);
-                    if (duration == expiryPolicy.getExpiryForAccess())
-                    {
-                        element.getElementAttributes().setIdleTime(duration.getTimeUnit().toMillis(duration.getDurationAmount()));
-                    }
-                    else
-                        {
-                        element.getElementAttributes().setMaxLife(duration.getTimeUnit().toMillis(duration.getDurationAmount()));
-                    }
-                }
-                element.setElementAttributes(copy);
-            }
-            writer.write(new JCSEntry<K, V>(jcsKey, value));
-            try
-            {
-                delegate.update(element);
-            }
-            catch (final IOException e)
-            {
-                throw new CacheException(e);
-            }
-            for (final JCSListener<K, V> listener : listeners.values())
-            {
-                if (created)
-                {
-                    listener.onCreated(Arrays.<CacheEntryEvent<? extends K, ? extends V>> asList(new JCSCacheEntryEvent<K, V>(this,
-                            EventType.CREATED, null, key, value)));
-                }
-                else
-                {
-                    listener.onUpdated(Arrays.<CacheEntryEvent<? extends K, ? extends V>> asList(new JCSCacheEntryEvent<K, V>(this,
-                            EventType.UPDATED, old, key, value)));
-                }
-            }
-
-            if (statisticsEnabled)
-            {
-                statistics.increasePuts(1);
-                statistics.addPutTime(System.currentTimeMillis() - start);
-            }
-        }
-        else
-        {
-            if (!created)
-            {
-                forceExpires(key);
-            }
-        }
-    }
-
-    private static boolean isNotZero(final Duration duration)
-    {
-        return duration == null || !duration.isZero();
-    }
-
-    private void forceExpires(final K cacheKey)
-    {
-        final ICacheElement<K, V> elt = delegate.get(cacheKey);
-        delegate.remove(cacheKey);
-        for (final JCSListener<K, V> listener : listeners.values())
-        {
-            listener.onExpired(Arrays.<CacheEntryEvent<? extends K, ? extends V>> asList(new JCSCacheEntryEvent<K, V>(this,
-                    EventType.REMOVED, null, cacheKey, elt.getVal())));
-        }
-    }
-
-    @Override
-    public V getAndPut(final K key, final V value)
-    {
-        assertNotClosed();
-        assertNotNull(key, "key");
-        assertNotNull(value, "value");
-        final long getStart = Times.now(false);
-        final V v = doGetControllingExpiry(getStart, key, false, false, true, false);
-        put(key, value);
-        return v;
-    }
-
-    @Override
-    public void putAll(final Map<? extends K, ? extends V> map)
-    {
-        assertNotClosed();
-        final TempStateCacheView<K, V> view = new TempStateCacheView<K, V>(this);
-        for (final Map.Entry<? extends K, ? extends V> e : map.entrySet())
-        {
-            view.put(e.getKey(), e.getValue());
-        }
-        view.merge();
-    }
-
-    @Override
-    public boolean putIfAbsent(final K key, final V value)
-    {
-        if (!containsKey(key))
-        {
-            put(key, value);
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    public boolean remove(final K key)
-    {
-        assertNotClosed();
-        assertNotNull(key, "key");
-
-        final boolean statisticsEnabled = config.isStatisticsEnabled();
-        final long start = Times.now(!statisticsEnabled);
-
-        writer.delete(key);
-        final K cacheKey = key;
-
-        final ICacheElement<K, V> v = delegate.get(cacheKey);
-        delegate.remove(cacheKey);
-
-        final V value = v != null && v.getVal() != null ? v.getVal() : null;
-        boolean remove = v != null;
-        for (final JCSListener<K, V> listener : listeners.values())
-        {
-            listener.onRemoved(Arrays.<CacheEntryEvent<? extends K, ? extends V>> asList(new JCSCacheEntryEvent<K, V>(this,
-                    EventType.REMOVED, null, key, value)));
-        }
-        if (remove && statisticsEnabled)
-        {
-            statistics.increaseRemovals(1);
-            statistics.addRemoveTime(Times.now(false) - start);
-        }
-        return remove;
-    }
-
-    @Override
-    public boolean remove(final K key, final V oldValue)
-    {
-        assertNotClosed();
-        assertNotNull(key, "key");
-        assertNotNull(oldValue, "oldValue");
-        final long getStart = Times.now(false);
-        final V v = doGetControllingExpiry(getStart, key, false, false, false, false);
-        if (oldValue.equals(v))
-        {
-            remove(key);
-            return true;
-        }
-        else if (v != null)
-        {
-            // weird but just for stats to be right (org.jsr107.tck.expiry.CacheExpiryTest.removeSpecifiedEntryShouldNotCallExpiryPolicyMethods())
-            expiryPolicy.getExpiryForAccess();
-        }
-        return false;
-    }
-
-    @Override
-    public V getAndRemove(final K key)
-    {
-        assertNotClosed();
-        assertNotNull(key, "key");
-        final long getStart = Times.now(false);
-        final V v = doGetControllingExpiry(getStart, key, false, false, true, false);
-        remove(key);
-        return v;
-    }
-
-    private V doGetControllingExpiry(final long getStart, final K key, final boolean updateAcess, final boolean forceDoLoad, final boolean skipLoad,
-            final boolean propagateLoadException)
-    {
-        final boolean statisticsEnabled = config.isStatisticsEnabled();
-        final ICacheElement<K, V> elt = delegate.get(key);
-        V v = elt != null ? elt.getVal() : null;
-        if (v == null && (config.isReadThrough() || forceDoLoad))
-        {
-            if (!skipLoad)
-            {
-                v = doLoad(key, false, getStart, propagateLoadException);
-            }
-        }
-        else if (statisticsEnabled)
-        {
-            if (v != null)
-            {
-                statistics.increaseHits(1);
-            }
-            else
-            {
-                statistics.increaseMisses(1);
-            }
-        }
-
-        if (updateAcess && elt != null)
-        {
-            final Duration expiryForAccess = expiryPolicy.getExpiryForAccess();
-            if (!isNotZero(expiryForAccess))
-            {
-                forceExpires(key);
-            }
-            else if (expiryForAccess != null && (!elt.getElementAttributes().getIsEternal() || !expiryForAccess.isEternal()))
-            {
-                try
-                {
-                    delegate.update(updateElement(key, elt.getVal(), expiryForAccess, elt.getElementAttributes()));
-                }
-                catch (final IOException e)
-                {
-                    throw new CacheException(e);
-                }
-            }
-        }
-        if (statisticsEnabled && v != null)
-        {
-            statistics.addGetTime(Times.now(false) - getStart);
-        }
-        return v;
-    }
-
-    @Override
-    public boolean replace(final K key, final V oldValue, final V newValue)
-    {
-        assertNotClosed();
-        assertNotNull(key, "key");
-        assertNotNull(oldValue, "oldValue");
-        assertNotNull(newValue, "newValue");
-        final boolean statisticsEnabled = config.isStatisticsEnabled();
-        final ICacheElement<K, V> elt = delegate.get(key);
-        if (elt != null)
-        {
-            V value = elt.getVal();
-            if (value != null && statisticsEnabled)
-            {
-                statistics.increaseHits(1);
-            }
-            if (value == null && config.isReadThrough())
-            {
-                value = doLoad(key, false, Times.now(false), false);
-            }
-            if (value != null && value.equals(oldValue))
-            {
-                put(key, newValue);
-                return true;
-            }
-            else if (value != null)
-            {
-                final Duration expiryForAccess = expiryPolicy.getExpiryForAccess();
-                if (expiryForAccess != null && (!elt.getElementAttributes().getIsEternal() || !expiryForAccess.isEternal()))
-                {
-                    try
-                    {
-                        delegate.update(updateElement(key, elt.getVal(), expiryForAccess, elt.getElementAttributes()));
-                    }
-                    catch (final IOException e)
-                    {
-                        throw new CacheException(e);
-                    }
-                }
-            }
-        }
-        else if (statisticsEnabled)
-        {
-            statistics.increaseMisses(1);
-        }
-        return false;
-    }
-
-    @Override
-    public boolean replace(final K key, final V value)
-    {
-        assertNotClosed();
-        assertNotNull(key, "key");
-        assertNotNull(value, "value");
-        boolean statisticsEnabled = config.isStatisticsEnabled();
-        if (containsKey(key))
-        {
-            if (statisticsEnabled)
-            {
-                statistics.increaseHits(1);
-            }
-            put(key, value);
-            return true;
-        }
-        else if (statisticsEnabled)
-        {
-            statistics.increaseMisses(1);
-        }
-        return false;
-    }
-
-    @Override
-    public V getAndReplace(final K key, final V value)
-    {
-        assertNotClosed();
-        assertNotNull(key, "key");
-        assertNotNull(value, "value");
-
-        final boolean statisticsEnabled = config.isStatisticsEnabled();
-
-        final ICacheElement<K, V> elt = delegate.get(key);
-        if (elt != null)
-        {
-            V oldValue = elt.getVal();
-            if (oldValue == null && config.isReadThrough())
-            {
-                oldValue = doLoad(key, false, Times.now(false), false);
-            }
-            else if (statisticsEnabled)
-            {
-                statistics.increaseHits(1);
-            }
-            put(key, value);
-            return oldValue;
-        }
-        else if (statisticsEnabled)
-        {
-            statistics.increaseMisses(1);
-        }
-        return null;
-    }
-
-    @Override
-    public void removeAll(final Set<? extends K> keys)
-    {
-        assertNotClosed();
-        assertNotNull(keys, "keys");
-        for (final K k : keys)
-        {
-            remove(k);
-        }
-    }
-
-    @Override
-    public void removeAll()
-    {
-        assertNotClosed();
-        for (final K k : delegate.getKeySet())
-        {
-            remove(k);
-        }
-    }
-
-    @Override
-    public void clear()
-    {
-        assertNotClosed();
-        try
-        {
-            delegate.removeAll();
-        }
-        catch (final IOException e)
-        {
-            throw new CacheException(e);
-        }
-    }
-
-    @Override
-    public <C2 extends Configuration<K, V>> C2 getConfiguration(final Class<C2> clazz)
-    {
-        assertNotClosed();
-        return clazz.cast(config);
-    }
-
-    @Override
-    public void loadAll(final Set<? extends K> keys, final boolean replaceExistingValues, final CompletionListener completionListener)
-    {
-        assertNotClosed();
-        assertNotNull(keys, "keys");
-        for (final K k : keys)
-        {
-            assertNotNull(k, "a key");
-        }
-        pool.submit(new Runnable()
-        {
-            @Override
-            public void run()
-            {
-                doLoadAll(keys, replaceExistingValues, completionListener);
-            }
-        });
-    }
-
-    private void doLoadAll(final Set<? extends K> keys, final boolean replaceExistingValues, final CompletionListener completionListener)
-    {
-        try
-        {
-            final long now = Times.now(false);
-            for (final K k : keys)
-            {
-                if (replaceExistingValues)
-                {
-                    doLoad(k, containsKey(k), now, completionListener != null);
-                    continue;
-                }
-                else if (containsKey(k))
-                {
-                    continue;
-                }
-                doGetControllingExpiry(now, k, true, true, false, completionListener != null);
-            }
-        }
-        catch (final RuntimeException e)
-        {
-            if (completionListener != null)
-            {
-                completionListener.onException(e);
-                return;
-            }
-        }
-        if (completionListener != null)
-        {
-            completionListener.onCompletion();
-        }
-    }
-
-    @Override
-    public <T> T invoke(final K key, final EntryProcessor<K, V, T> entryProcessor, final Object... arguments) throws EntryProcessorException
-    {
-        final TempStateCacheView<K, V> view = new TempStateCacheView<K, V>(this);
-        final T t = doInvoke(view, key, entryProcessor, arguments);
-        view.merge();
-        return t;
-    }
-
-    private <T> T doInvoke(final TempStateCacheView<K, V> view, final K key, final EntryProcessor<K, V, T> entryProcessor,
-            final Object... arguments)
-    {
-        assertNotClosed();
-        assertNotNull(entryProcessor, "entryProcessor");
-        assertNotNull(key, "key");
-        try
-        {
-            if (config.isStatisticsEnabled())
-            {
-                if (containsKey(key))
-                {
-                    statistics.increaseHits(1);
-                }
-                else
-                {
-                    statistics.increaseMisses(1);
-                }
-            }
-            return entryProcessor.process(new JCSMutableEntry<K, V>(view, key), arguments);
-        }
-        catch (final Exception ex)
-        {
-            return throwEntryProcessorException(ex);
-        }
-    }
-
-    private static <T> T throwEntryProcessorException(final Exception ex)
-    {
-        if (EntryProcessorException.class.isInstance(ex))
-        {
-            throw EntryProcessorException.class.cast(ex);
-        }
-        throw new EntryProcessorException(ex);
-    }
-
-    @Override
-    public <T> Map<K, EntryProcessorResult<T>> invokeAll(final Set<? extends K> keys, final EntryProcessor<K, V, T> entryProcessor,
-            final Object... arguments)
-    {
-        assertNotClosed();
-        assertNotNull(entryProcessor, "entryProcessor");
-        final Map<K, EntryProcessorResult<T>> results = new HashMap<K, EntryProcessorResult<T>>();
-        for (final K k : keys)
-        {
-            try
-            {
-                final T invoke = invoke(k, entryProcessor, arguments);
-                if (invoke != null)
-                {
-                    results.put(k, new EntryProcessorResult<T>()
-                    {
-                        @Override
-                        public T get() throws EntryProcessorException
-                        {
-                            return invoke;
-                        }
-                    });
-                }
-            }
-            catch (final Exception e)
-            {
-                results.put(k, new EntryProcessorResult<T>()
-                {
-                    @Override
-                    public T get() throws EntryProcessorException
-                    {
-                        return throwEntryProcessorException(e);
-                    }
-                });
-            }
-        }
-        return results;
-    }
-
-    @Override
-    public void registerCacheEntryListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration)
-    {
-        assertNotClosed();
-        if (listeners.containsKey(cacheEntryListenerConfiguration))
-        {
-            throw new IllegalArgumentException(cacheEntryListenerConfiguration + " already registered");
-        }
-        listeners.put(cacheEntryListenerConfiguration, new JCSListener<K, V>(cacheEntryListenerConfiguration));
-        config.addListener(cacheEntryListenerConfiguration);
-    }
-
-    @Override
-    public void deregisterCacheEntryListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration)
-    {
-        assertNotClosed();
-        listeners.remove(cacheEntryListenerConfiguration);
-        config.removeListener(cacheEntryListenerConfiguration);
-    }
-
-    @Override
-    public Iterator<Entry<K, V>> iterator()
-    {
-        assertNotClosed();
-        final Iterator<K> keys = new HashSet<K>(delegate.getKeySet()).iterator();
-        return new Iterator<Entry<K, V>>()
-        {
-            private K lastKey = null;
-
-            @Override
-            public boolean hasNext()
-            {
-                return keys.hasNext();
-            }
-
-            @Override
-            public Entry<K, V> next()
-            {
-                lastKey = keys.next();
-                return new JCSEntry<K, V>(lastKey, get(lastKey));
-            }
-
-            @Override
-            public void remove()
-            {
-                if (isClosed() || lastKey == null)
-                {
-                    throw new IllegalStateException(isClosed() ? "cache closed" : "call next() before remove()");
-                }
-                JCSCache.this.remove(lastKey);
-            }
-        };
-    }
-
-    @Override
-    public String getName()
-    {
-        assertNotClosed();
-        return name;
-    }
-
-    @Override
-    public CacheManager getCacheManager()
-    {
-        assertNotClosed();
-        return manager;
-    }
-
-    @Override
-    public synchronized void close()
-    {
-        if (isClosed())
-        {
-            return;
-        }
-
-        for (final Runnable task : pool.shutdownNow()) {
-            task.run();
-        }
-
-        manager.release(getName());
-        closed = true;
-        close(loader);
-        close(writer);
-        close(expiryPolicy);
-        for (final JCSListener<K, V> listener : listeners.values())
-        {
-            close(listener);
-        }
-        listeners.clear();
-        JMXs.unregister(cacheConfigObjectName);
-        JMXs.unregister(cacheStatsObjectName);
-        try
-        {
-            delegate.removeAll();
-        }
-        catch (final IOException e)
-        {
-            throw new CacheException(e);
-        }
-    }
-
-    private static void close(final Object potentiallyCloseable)
-    {
-        if (Closeable.class.isInstance(potentiallyCloseable))
-        {
-            Closeable.class.cast(potentiallyCloseable);
-        }
-    }
-
-    @Override
-    public boolean isClosed()
-    {
-        return closed;
-    }
-
-    @Override
-    public <T> T unwrap(final Class<T> clazz)
-    {
-        assertNotClosed();
-        if (clazz.isInstance(this))
-        {
-            return clazz.cast(this);
-        }
-        if (clazz.isAssignableFrom(Map.class) || clazz.isAssignableFrom(ConcurrentMap.class))
-        {
-            return clazz.cast(delegate);
-        }
-        throw new IllegalArgumentException(clazz.getName() + " not supported in unwrap");
-    }
-
-    public Statistics getStatistics()
-    {
-        return statistics;
-    }
-
-    public void enableManagement()
-    {
-        config.managementEnabled();
-        JMXs.register(cacheConfigObjectName, new JCSCacheMXBean<K, V>(this));
-    }
-
-    public void disableManagement()
-    {
-        config.managementDisabled();
-        JMXs.unregister(cacheConfigObjectName);
-    }
-
-    public void enableStatistics()
-    {
-        config.statisticsEnabled();
-        statistics.setActive(true);
-        JMXs.register(cacheStatsObjectName, new JCSCacheStatisticsMXBean(statistics));
-    }
-
-    public void disableStatistics()
-    {
-        config.statisticsDisabled();
-        statistics.setActive(false);
-        JMXs.unregister(cacheStatsObjectName);
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/JCSCacheEntryEvent.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/JCSCacheEntryEvent.java
deleted file mode 100644
index 54a6f87..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/JCSCacheEntryEvent.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache;
-
-import javax.cache.Cache;
-import javax.cache.event.CacheEntryEvent;
-import javax.cache.event.EventType;
-
-public class JCSCacheEntryEvent<K, V> extends CacheEntryEvent<K, V>
-{
-    /** Serial version */
-    private static final long serialVersionUID = 4761272981003897488L;
-
-    private final V old;
-    private final K key;
-    private final V value;
-
-    public JCSCacheEntryEvent(final Cache<K, V> source, final EventType eventType, final V old, final K key, final V value)
-    {
-        super(source, eventType);
-        this.old = old;
-        this.key = key;
-        this.value = value;
-    }
-
-    @Override
-    public V getOldValue()
-    {
-        return old;
-    }
-
-    @Override
-    public boolean isOldValueAvailable()
-    {
-        return old != null;
-    }
-
-    @Override
-    public K getKey()
-    {
-        return key;
-    }
-
-    @Override
-    public V getValue()
-    {
-        return value;
-    }
-
-    @Override
-    public <T> T unwrap(final Class<T> clazz)
-    {
-        if (clazz.isInstance(this))
-        {
-            return clazz.cast(this);
-        }
-        throw new IllegalArgumentException(clazz.getName() + " not supported in unwrap");
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/JCSCachingManager.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/JCSCachingManager.java
deleted file mode 100644
index c764eb3..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/JCSCachingManager.java
+++ /dev/null
@@ -1,398 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache;
-
-import org.apache.commons.jcs.engine.behavior.ICompositeCacheAttributes;
-import org.apache.commons.jcs.engine.behavior.IElementAttributes;
-import org.apache.commons.jcs.engine.control.CompositeCache;
-import org.apache.commons.jcs.engine.control.CompositeCacheConfigurator;
-import org.apache.commons.jcs.engine.control.CompositeCacheManager;
-import org.apache.commons.jcs.jcache.lang.Subsitutor;
-import org.apache.commons.jcs.jcache.proxy.ClassLoaderAwareCache;
-
-import javax.cache.Cache;
-import javax.cache.CacheManager;
-import javax.cache.configuration.Configuration;
-import javax.cache.spi.CachingProvider;
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URI;
-import java.net.URL;
-import java.util.Enumeration;
-import java.util.Map;
-import java.util.Properties;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-import static org.apache.commons.jcs.jcache.Asserts.assertNotNull;
-
-public class JCSCachingManager implements CacheManager
-{
-    private static final Subsitutor SUBSTITUTOR = Subsitutor.Helper.INSTANCE;
-    private static final String DEFAULT_CONFIG =
-        "jcs.default=DC\n" +
-        "jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes\n" +
-        "jcs.default.cacheattributes.MaxObjects=200001\n" +
-        "jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache\n" +
-        "jcs.default.cacheattributes.UseMemoryShrinker=true\n" +
-        "jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600\n" +
-        "jcs.default.cacheattributes.ShrinkerIntervalSeconds=60\n" +
-        "jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes\n" +
-        "jcs.default.elementattributes.IsEternal=false\n" +
-        "jcs.default.elementattributes.MaxLife=700\n" +
-        "jcs.default.elementattributes.IdleTime=1800\n" +
-        "jcs.default.elementattributes.IsSpool=true\n" +
-        "jcs.default.elementattributes.IsRemote=true\n" +
-        "jcs.default.elementattributes.IsLateral=true\n";
-
-    private static class InternalManager extends CompositeCacheManager
-    {
-        protected static InternalManager create()
-        {
-            return new InternalManager();
-        }
-
-        protected CompositeCacheConfigurator newConfigurator()
-        {
-            return new CompositeCacheConfigurator()
-            {
-                @Override
-                protected <K, V> CompositeCache<K, V> newCache(
-                        final ICompositeCacheAttributes cca, final IElementAttributes ea)
-                {
-                    return new ExpiryAwareCache<K, V>( cca, ea );
-                }
-            };
-        }
-
-        @Override // needed to call it from JCSCachingManager
-        protected void initialize() {
-            super.initialize();
-        }
-    }
-
-    private final CachingProvider provider;
-    private final URI uri;
-    private final ClassLoader loader;
-    private final Properties properties;
-    private final ConcurrentMap<String, Cache<?, ?>> caches = new ConcurrentHashMap<String, Cache<?, ?>>();
-    private final Properties configProperties;
-    private volatile boolean closed = false;
-    private InternalManager delegate = InternalManager.create();
-
-    public JCSCachingManager(final CachingProvider provider, final URI uri, final ClassLoader loader, final Properties properties)
-    {
-        this.provider = provider;
-        this.uri = uri;
-        this.loader = loader;
-        this.properties = readConfig(uri, loader, properties);
-        this.configProperties = properties;
-
-        delegate.setJmxName(CompositeCacheManager.JMX_OBJECT_NAME
-                + ",provider=" + provider.hashCode()
-                + ",uri=" + uri.toString().replaceAll(",|:|=|\n", ".")
-                + ",classloader=" + loader.hashCode()
-                + ",properties=" + this.properties.hashCode());
-        delegate.initialize();
-        delegate.configure(this.properties);
-    }
-
-    private Properties readConfig(final URI uri, final ClassLoader loader, final Properties properties) {
-        final Properties props = new Properties();
-        try {
-            if (JCSCachingProvider.DEFAULT_URI.toString().equals(uri.toString()) || uri.toURL().getProtocol().equals("jcs"))
-            {
-
-                final Enumeration<URL> resources = loader.getResources(uri.getPath());
-                if (!resources.hasMoreElements()) // default
-                {
-                    props.load(new ByteArrayInputStream(DEFAULT_CONFIG.getBytes("UTF-8")));
-                }
-                else
-                {
-                    do
-                    {
-                        addProperties(resources.nextElement(), props);
-                    }
-                    while (resources.hasMoreElements());
-                }
-            }
-            else
-            {
-                props.load(uri.toURL().openStream());
-            }
-        } catch (final IOException e) {
-            throw new IllegalStateException(e);
-        }
-
-        if (properties != null)
-        {
-            props.putAll(properties);
-        }
-
-        for (final Map.Entry<Object, Object> entry : props.entrySet()) {
-            if (entry.getValue() == null)
-            {
-                continue;
-            }
-            final String substitute = SUBSTITUTOR.substitute(entry.getValue().toString());
-            if (!substitute.equals(entry.getValue()))
-            {
-                entry.setValue(substitute);
-            }
-        }
-        return props;
-    }
-
-    private void addProperties(final URL url, final Properties aggregator)
-    {
-        InputStream inStream = null;
-        try
-        {
-            inStream = url.openStream();
-            aggregator.load(inStream);
-        }
-        catch (final IOException e)
-        {
-            throw new IllegalArgumentException(e);
-        }
-        finally
-        {
-            if (inStream != null)
-            {
-                try
-                {
-                    inStream.close();
-                }
-                catch (final IOException e)
-                {
-                    // no-op
-                }
-            }
-        }
-    }
-
-    private void assertNotClosed()
-    {
-        if (isClosed())
-        {
-            throw new IllegalStateException("cache manager closed");
-        }
-    }
-
-    @Override
-    // TODO: use configuration + handle not serializable key/values
-    public <K, V, C extends Configuration<K, V>> Cache<K, V> createCache(final String cacheName, final C configuration)
-            throws IllegalArgumentException
-    {
-        assertNotClosed();
-        assertNotNull(cacheName, "cacheName");
-        assertNotNull(configuration, "configuration");
-        final Class<?> keyType = configuration == null ? Object.class : configuration.getKeyType();
-        final Class<?> valueType = configuration == null ? Object.class : configuration.getValueType();
-        if (!caches.containsKey(cacheName))
-        {
-            final Cache<K, V> cache = ClassLoaderAwareCache.wrap(loader,
-                    new JCSCache/*<K, V>*/(
-                            loader, this, cacheName,
-                            new JCSConfiguration/*<K, V>*/(configuration, keyType, valueType),
-                            properties,
-                            ExpiryAwareCache.class.cast(delegate.getCache(cacheName))));
-            caches.putIfAbsent(cacheName, cache);
-        }
-        else
-        {
-            throw new javax.cache.CacheException("cache " + cacheName + " already exists");
-        }
-        return (Cache<K, V>) getCache(cacheName, keyType, valueType);
-    }
-
-    @Override
-    public void destroyCache(final String cacheName)
-    {
-        assertNotClosed();
-        assertNotNull(cacheName, "cacheName");
-        final Cache<?, ?> cache = caches.remove(cacheName);
-        if (cache != null && !cache.isClosed())
-        {
-            cache.clear();
-            cache.close();
-        }
-    }
-
-    @Override
-    public void enableManagement(final String cacheName, final boolean enabled)
-    {
-        assertNotClosed();
-        assertNotNull(cacheName, "cacheName");
-        final JCSCache<?, ?> cache = getJCSCache(cacheName);
-        if (cache != null)
-        {
-            if (enabled)
-            {
-                cache.enableManagement();
-            }
-            else
-            {
-                cache.disableManagement();
-            }
-        }
-    }
-
-    private JCSCache<?, ?> getJCSCache(final String cacheName)
-    {
-        final Cache<?, ?> cache = caches.get(cacheName);
-        return JCSCache.class.cast(ClassLoaderAwareCache.getDelegate(cache));
-    }
-
-    @Override
-    public void enableStatistics(final String cacheName, final boolean enabled)
-    {
-        assertNotClosed();
-        assertNotNull(cacheName, "cacheName");
-        final JCSCache<?, ?> cache = getJCSCache(cacheName);
-        if (cache != null)
-        {
-            if (enabled)
-            {
-                cache.enableStatistics();
-            }
-            else
-            {
-                cache.disableStatistics();
-            }
-        }
-    }
-
-    @Override
-    public synchronized void close()
-    {
-        if (isClosed())
-        {
-            return;
-        }
-
-        assertNotClosed();
-        for (final Cache<?, ?> c : caches.values())
-        {
-            c.close();
-        }
-        caches.clear();
-        closed = true;
-        if (JCSCachingProvider.class.isInstance(provider))
-        {
-            JCSCachingProvider.class.cast(provider).remove(this);
-        }
-        delegate.shutDown();
-    }
-
-    @Override
-    public <T> T unwrap(final Class<T> clazz)
-    {
-        if (clazz.isInstance(this))
-        {
-            return clazz.cast(this);
-        }
-        throw new IllegalArgumentException(clazz.getName() + " not supported in unwrap");
-    }
-
-    @Override
-    public boolean isClosed()
-    {
-        return closed;
-    }
-
-    @Override
-    public <K, V> Cache<K, V> getCache(final String cacheName)
-    {
-        assertNotClosed();
-        assertNotNull(cacheName, "cacheName");
-        return (Cache<K, V>) doGetCache(cacheName, Object.class, Object.class);
-    }
-
-    @Override
-    public Iterable<String> getCacheNames()
-    {
-        return new ImmutableIterable<String>(caches.keySet());
-    }
-
-    @Override
-    public <K, V> Cache<K, V> getCache(final String cacheName, final Class<K> keyType, final Class<V> valueType)
-    {
-        assertNotClosed();
-        assertNotNull(cacheName, "cacheName");
-        assertNotNull(keyType, "keyType");
-        assertNotNull(valueType, "valueType");
-        try
-        {
-            return doGetCache(cacheName, keyType, valueType);
-        }
-        catch (final IllegalArgumentException iae)
-        {
-            throw new ClassCastException(iae.getMessage());
-        }
-    }
-
-    private <K, V> Cache<K, V> doGetCache(final String cacheName, final Class<K> keyType, final Class<V> valueType)
-    {
-        final Cache<K, V> cache = (Cache<K, V>) caches.get(cacheName);
-        if (cache == null)
-        {
-            return null;
-        }
-
-        final Configuration<K, V> config = cache.getConfiguration(Configuration.class);
-        if ((keyType != null && !config.getKeyType().isAssignableFrom(keyType))
-                || (valueType != null && !config.getValueType().isAssignableFrom(valueType)))
-        {
-            throw new IllegalArgumentException("this cache is <" + config.getKeyType().getName() + ", " + config.getValueType().getName()
-                    + "> " + " and not <" + keyType.getName() + ", " + valueType.getName() + ">");
-        }
-        return cache;
-    }
-
-    @Override
-    public CachingProvider getCachingProvider()
-    {
-        return provider;
-    }
-
-    @Override
-    public URI getURI()
-    {
-        return uri;
-    }
-
-    @Override
-    public ClassLoader getClassLoader()
-    {
-        return loader;
-    }
-
-    @Override
-    public Properties getProperties()
-    {
-        return configProperties;
-    }
-
-    public void release(final String name) {
-        caches.remove(name);
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/JCSCachingProvider.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/JCSCachingProvider.java
deleted file mode 100644
index 128de3a..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/JCSCachingProvider.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache;
-
-import javax.cache.CacheManager;
-import javax.cache.configuration.OptionalFeature;
-import javax.cache.spi.CachingProvider;
-import java.net.URI;
-import java.util.Map;
-import java.util.Properties;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-public class JCSCachingProvider implements CachingProvider
-{
-    public static final URI DEFAULT_URI = URI.create("jcs://jcache.ccf");
-
-    private final ConcurrentMap<ClassLoader, ConcurrentMap<URI, CacheManager>> cacheManagersByLoader = new ConcurrentHashMap<ClassLoader, ConcurrentMap<URI, CacheManager>>();
-
-    @Override
-    public CacheManager getCacheManager(final URI inUri, final ClassLoader inClassLoader, final Properties properties)
-    {
-        final URI uri = inUri != null ? inUri : getDefaultURI();
-        final ClassLoader classLoader = inClassLoader != null ? inClassLoader : getDefaultClassLoader();
-
-        ConcurrentMap<URI, CacheManager> managers = cacheManagersByLoader.get(classLoader);
-        if (managers == null)
-        {
-            managers = new ConcurrentHashMap<URI, CacheManager>();
-            final ConcurrentMap<URI, CacheManager> existingManagers = cacheManagersByLoader.putIfAbsent(classLoader, managers);
-            if (existingManagers != null)
-            {
-                managers = existingManagers;
-            }
-        }
-
-        CacheManager mgr = managers.get(uri);
-        if (mgr == null)
-        {
-            mgr = new JCSCachingManager(this, uri, classLoader, properties);
-            final CacheManager existing = managers.putIfAbsent(uri, mgr);
-            if (existing != null)
-            {
-                mgr = existing;
-            }
-        }
-
-        return mgr;
-    }
-
-    @Override
-    public URI getDefaultURI()
-    {
-        return DEFAULT_URI;
-    }
-
-    @Override
-    public void close()
-    {
-        for (final Map<URI, CacheManager> v : cacheManagersByLoader.values())
-        {
-            for (final CacheManager m : v.values())
-            {
-                m.close();
-            }
-            v.clear();
-        }
-        cacheManagersByLoader.clear();
-    }
-
-    @Override
-    public void close(final ClassLoader classLoader)
-    {
-        final Map<URI, CacheManager> cacheManagers = cacheManagersByLoader.remove(classLoader);
-        if (cacheManagers != null)
-        {
-            for (final CacheManager mgr : cacheManagers.values())
-            {
-                mgr.close();
-            }
-            cacheManagers.clear();
-        }
-    }
-
-    @Override
-    public void close(final URI uri, final ClassLoader classLoader)
-    {
-        final Map<URI, CacheManager> cacheManagers = cacheManagersByLoader.remove(classLoader);
-        if (cacheManagers != null)
-        {
-            final CacheManager mgr = cacheManagers.remove(uri);
-            if (mgr != null)
-            {
-                mgr.close();
-            }
-        }
-    }
-
-    @Override
-    public CacheManager getCacheManager(final URI uri, final ClassLoader classLoader)
-    {
-        return getCacheManager(uri, classLoader, getDefaultProperties());
-    }
-
-    @Override
-    public CacheManager getCacheManager()
-    {
-        return getCacheManager(getDefaultURI(), getDefaultClassLoader());
-    }
-
-    @Override
-    public boolean isSupported(final OptionalFeature optionalFeature)
-    {
-        return optionalFeature == OptionalFeature.STORE_BY_REFERENCE;
-    }
-
-    @Override
-    public ClassLoader getDefaultClassLoader()
-    {
-        return JCSCachingProvider.class.getClassLoader();
-    }
-
-    @Override
-    public Properties getDefaultProperties()
-    {
-        return new Properties();
-    }
-
-    void remove(final CacheManager mgr)
-    {
-        final ClassLoader classLoader = mgr.getClassLoader();
-        final Map<URI, CacheManager> mgrs = cacheManagersByLoader.get(classLoader);
-        if (mgrs != null)
-        {
-            mgrs.remove(mgr.getURI());
-            if (mgrs.isEmpty())
-            {
-                cacheManagersByLoader.remove(classLoader);
-            }
-        }
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/JCSConfiguration.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/JCSConfiguration.java
deleted file mode 100644
index 9dffdba..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/JCSConfiguration.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-/**
- *  Copyright 2003-2010 Terracotta, Inc.
- *
- *  Licensed under the Apache License, Version 2.0 (the "License");
- *  you may not use this file except in compliance with the License.
- *  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-package org.apache.commons.jcs.jcache;
-
-import javax.cache.configuration.CacheEntryListenerConfiguration;
-import javax.cache.configuration.CompleteConfiguration;
-import javax.cache.configuration.Configuration;
-import javax.cache.configuration.Factory;
-import javax.cache.expiry.EternalExpiryPolicy;
-import javax.cache.expiry.ExpiryPolicy;
-import javax.cache.integration.CacheLoader;
-import javax.cache.integration.CacheWriter;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-public class JCSConfiguration<K, V> implements CompleteConfiguration<K, V>
-{
-
-    private final Class<K> keyType;
-    private final Class<V> valueType;
-    private final boolean storeByValue;
-    private final boolean readThrough;
-    private final boolean writeThrough;
-    private final Factory<CacheLoader<K, V>> cacheLoaderFactory;
-    private final Factory<CacheWriter<? super K, ? super V>> cacheWristerFactory;
-    private final Factory<ExpiryPolicy> expiryPolicyFactory;
-    private final Set<CacheEntryListenerConfiguration<K, V>> cacheEntryListenerConfigurations;
-
-    private volatile boolean statisticsEnabled;
-    private volatile boolean managementEnabled;
-
-    public JCSConfiguration(final Configuration<K, V> configuration, final Class<K> keyType, final Class<V> valueType)
-    {
-        this.keyType = keyType;
-        this.valueType = valueType;
-        if (configuration instanceof CompleteConfiguration)
-        {
-            final CompleteConfiguration<K, V> cConfiguration = (CompleteConfiguration<K, V>) configuration;
-            storeByValue = configuration.isStoreByValue();
-            readThrough = cConfiguration.isReadThrough();
-            writeThrough = cConfiguration.isWriteThrough();
-            statisticsEnabled = cConfiguration.isStatisticsEnabled();
-            managementEnabled = cConfiguration.isManagementEnabled();
-            cacheLoaderFactory = cConfiguration.getCacheLoaderFactory();
-            cacheWristerFactory = cConfiguration.getCacheWriterFactory();
-            this.expiryPolicyFactory = cConfiguration.getExpiryPolicyFactory();
-            cacheEntryListenerConfigurations = new HashSet<CacheEntryListenerConfiguration<K, V>>();
-
-            final Iterable<CacheEntryListenerConfiguration<K, V>> entryListenerConfigurations = cConfiguration
-                    .getCacheEntryListenerConfigurations();
-            if (entryListenerConfigurations != null)
-            {
-                for (final CacheEntryListenerConfiguration<K, V> kvCacheEntryListenerConfiguration : entryListenerConfigurations)
-                {
-                    cacheEntryListenerConfigurations.add(kvCacheEntryListenerConfiguration);
-                }
-            }
-        }
-        else
-        {
-            expiryPolicyFactory = EternalExpiryPolicy.factoryOf();
-            storeByValue = true;
-            readThrough = false;
-            writeThrough = false;
-            statisticsEnabled = false;
-            managementEnabled = false;
-            cacheLoaderFactory = null;
-            cacheWristerFactory = null;
-            cacheEntryListenerConfigurations = new HashSet<CacheEntryListenerConfiguration<K, V>>();
-        }
-    }
-
-    @Override
-    public Class<K> getKeyType()
-    {
-        return keyType == null ? (Class<K>) Object.class : keyType;
-    }
-
-    @Override
-    public Class<V> getValueType()
-    {
-        return valueType == null ? (Class<V>) Object.class : valueType;
-    }
-
-    @Override
-    public boolean isStoreByValue()
-    {
-        return storeByValue;
-    }
-
-    @Override
-    public boolean isReadThrough()
-    {
-        return readThrough;
-    }
-
-    @Override
-    public boolean isWriteThrough()
-    {
-        return writeThrough;
-    }
-
-    @Override
-    public boolean isStatisticsEnabled()
-    {
-        return statisticsEnabled;
-    }
-
-    @Override
-    public boolean isManagementEnabled()
-    {
-        return managementEnabled;
-    }
-
-    @Override
-    public Iterable<CacheEntryListenerConfiguration<K, V>> getCacheEntryListenerConfigurations()
-    {
-        return Collections.unmodifiableSet(cacheEntryListenerConfigurations);
-    }
-
-    @Override
-    public Factory<CacheLoader<K, V>> getCacheLoaderFactory()
-    {
-        return cacheLoaderFactory;
-    }
-
-    @Override
-    public Factory<CacheWriter<? super K, ? super V>> getCacheWriterFactory()
-    {
-        return cacheWristerFactory;
-    }
-
-    @Override
-    public Factory<ExpiryPolicy> getExpiryPolicyFactory()
-    {
-        return expiryPolicyFactory;
-    }
-
-    public synchronized void addListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration)
-    {
-        cacheEntryListenerConfigurations.add(cacheEntryListenerConfiguration);
-    }
-
-    public synchronized void removeListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration)
-    {
-        cacheEntryListenerConfigurations.remove(cacheEntryListenerConfiguration);
-    }
-
-    public void statisticsEnabled()
-    {
-        statisticsEnabled = true;
-    }
-
-    public void managementEnabled()
-    {
-        managementEnabled = true;
-    }
-
-    public void statisticsDisabled()
-    {
-        statisticsEnabled = false;
-    }
-
-    public void managementDisabled()
-    {
-        managementEnabled = false;
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/JCSEntry.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/JCSEntry.java
deleted file mode 100644
index b0382ac..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/JCSEntry.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache;
-
-import javax.cache.Cache;
-
-public class JCSEntry<K, V> implements Cache.Entry<K, V>
-{
-    private final K key;
-    private final V value;
-
-    public JCSEntry(final K key, final V value)
-    {
-        this.key = key;
-        this.value = value;
-    }
-
-    @Override
-    public K getKey()
-    {
-        return key;
-    }
-
-    @Override
-    public V getValue()
-    {
-        return value;
-    }
-
-    @Override
-    public <T> T unwrap(final Class<T> clazz)
-    {
-        if (clazz.isInstance(this))
-        {
-            return clazz.cast(this);
-        }
-        throw new UnsupportedOperationException();
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/JCSListener.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/JCSListener.java
deleted file mode 100644
index 930a533..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/JCSListener.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache;
-
-import javax.cache.configuration.CacheEntryListenerConfiguration;
-import javax.cache.configuration.Factory;
-import javax.cache.event.CacheEntryCreatedListener;
-import javax.cache.event.CacheEntryEvent;
-import javax.cache.event.CacheEntryEventFilter;
-import javax.cache.event.CacheEntryExpiredListener;
-import javax.cache.event.CacheEntryListener;
-import javax.cache.event.CacheEntryListenerException;
-import javax.cache.event.CacheEntryRemovedListener;
-import javax.cache.event.CacheEntryUpdatedListener;
-import java.io.Closeable;
-import java.util.ArrayList;
-import java.util.List;
-
-public class JCSListener<K, V> implements Closeable
-{
-    private final boolean oldValue;
-    private final boolean synchronous;
-    private final CacheEntryEventFilter<? super K, ? super V> filter;
-    private final CacheEntryListener<? super K, ? super V> delegate;
-    private final boolean remove;
-    private final boolean expire;
-    private final boolean update;
-    private final boolean create;
-
-    public JCSListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration)
-    {
-        oldValue = cacheEntryListenerConfiguration.isOldValueRequired();
-        synchronous = cacheEntryListenerConfiguration.isSynchronous();
-
-        final Factory<CacheEntryEventFilter<? super K, ? super V>> filterFactory = cacheEntryListenerConfiguration
-                .getCacheEntryEventFilterFactory();
-        if (filterFactory == null)
-        {
-            filter = NoFilter.INSTANCE;
-        }
-        else
-        {
-            filter = filterFactory.create();
-        }
-
-        delegate = cacheEntryListenerConfiguration.getCacheEntryListenerFactory().create();
-        remove = CacheEntryRemovedListener.class.isInstance(delegate);
-        expire = CacheEntryExpiredListener.class.isInstance(delegate);
-        update = CacheEntryUpdatedListener.class.isInstance(delegate);
-        create = CacheEntryCreatedListener.class.isInstance(delegate);
-    }
-
-    public void onRemoved(final List<CacheEntryEvent<? extends K, ? extends V>> events) throws CacheEntryListenerException
-    {
-        if (remove)
-        {
-            CacheEntryRemovedListener.class.cast(delegate).onRemoved(filter(events));
-        }
-    }
-
-    public void onExpired(final List<CacheEntryEvent<? extends K, ? extends V>> events) throws CacheEntryListenerException
-    {
-        if (expire)
-        {
-            CacheEntryExpiredListener.class.cast(delegate).onExpired(filter(events));
-        }
-    }
-
-    public void onUpdated(final List<CacheEntryEvent<? extends K, ? extends V>> events) throws CacheEntryListenerException
-    {
-        if (update)
-        {
-            CacheEntryUpdatedListener.class.cast(delegate).onUpdated(filter(events));
-        }
-    }
-
-    public void onCreated(final List<CacheEntryEvent<? extends K, ? extends V>> events) throws CacheEntryListenerException
-    {
-        if (create)
-        {
-            CacheEntryCreatedListener.class.cast(delegate).onCreated(filter(events));
-        }
-    }
-
-    private Iterable<CacheEntryEvent<? extends K, ? extends V>> filter(final List<CacheEntryEvent<? extends K, ? extends V>> events)
-    {
-        if (filter == NoFilter.INSTANCE)
-        {
-            return events;
-        }
-
-        final List<CacheEntryEvent<? extends K, ? extends V>> filtered = new ArrayList<CacheEntryEvent<? extends K, ? extends V>>(
-                events.size());
-        for (final CacheEntryEvent<? extends K, ? extends V> event : events)
-        {
-            if (filter.evaluate(event))
-            {
-                filtered.add(event);
-            }
-        }
-        return filtered;
-    }
-
-    @Override
-    public void close()
-    {
-        if (Closeable.class.isInstance(delegate)) {
-            Closeable.class.cast(delegate);
-        }
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/JCSMutableEntry.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/JCSMutableEntry.java
deleted file mode 100644
index a90ae85..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/JCSMutableEntry.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache;
-
-import javax.cache.Cache;
-import javax.cache.processor.MutableEntry;
-
-public class JCSMutableEntry<K, V> implements MutableEntry<K, V>
-{
-    private final Cache<K, V> cache;
-    private final K key;
-
-    public JCSMutableEntry(final Cache<K, V> cache, final K key)
-    {
-        this.cache = cache;
-        this.key = key;
-    }
-
-    @Override
-    public boolean exists()
-    {
-        return cache.containsKey(key);
-    }
-
-    @Override
-    public void remove()
-    {
-        cache.remove(key);
-    }
-
-    @Override
-    public void setValue(final V value)
-    {
-        cache.put(key, value);
-    }
-
-    @Override
-    public K getKey()
-    {
-        return key;
-    }
-
-    @Override
-    public V getValue()
-    {
-        return cache.get(key);
-    }
-
-    @Override
-    public <T> T unwrap(final Class<T> clazz)
-    {
-        if (clazz.isInstance(this))
-        {
-            return clazz.cast(this);
-        }
-        throw new IllegalArgumentException(clazz.getName() + " not supported in unwrap");
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/NoFilter.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/NoFilter.java
deleted file mode 100644
index 3315992..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/NoFilter.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache;
-
-import javax.cache.event.CacheEntryEvent;
-import javax.cache.event.CacheEntryEventFilter;
-import javax.cache.event.CacheEntryListenerException;
-
-public class NoFilter implements CacheEntryEventFilter<Object, Object>
-{
-    public static final CacheEntryEventFilter<Object, Object> INSTANCE = new NoFilter();
-
-    private NoFilter()
-    {
-        // no-op
-    }
-
-    @Override
-    public boolean evaluate(final CacheEntryEvent<?, ?> event) throws CacheEntryListenerException
-    {
-        return true;
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/NoLoader.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/NoLoader.java
deleted file mode 100644
index 740cac1..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/NoLoader.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache;
-
-import javax.cache.integration.CacheLoader;
-import javax.cache.integration.CacheLoaderException;
-import java.util.HashMap;
-import java.util.Map;
-
-public class NoLoader<K, V> implements CacheLoader<K, V>
-{
-    public static final NoLoader INSTANCE = new NoLoader();
-
-    private NoLoader()
-    {
-        // no-op
-    }
-
-    @Override
-    public V load(K key) throws CacheLoaderException
-    {
-        return null;
-    }
-
-    @Override
-    public Map<K, V> loadAll(final Iterable<? extends K> keys) throws CacheLoaderException
-    {
-        final Map<K, V> entries = new HashMap<K, V>();
-        for (final K k : keys)
-        {
-            entries.put(k, null);
-        }
-        return entries;
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/NoWriter.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/NoWriter.java
deleted file mode 100644
index 8aeba89..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/NoWriter.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache;
-
-import javax.cache.Cache;
-import javax.cache.integration.CacheWriter;
-import javax.cache.integration.CacheWriterException;
-import java.util.Collection;
-
-public class NoWriter<K, V> implements CacheWriter<K, V>
-{
-    public static final NoWriter INSTANCE = new NoWriter();
-
-    @Override
-    public void write(final Cache.Entry<? extends K, ? extends V> entry) throws CacheWriterException
-    {
-        // no-op
-    }
-
-    @Override
-    public void delete(final Object key) throws CacheWriterException
-    {
-        // no-op
-    }
-
-    @Override
-    public void writeAll(final Collection<Cache.Entry<? extends K, ? extends V>> entries) throws CacheWriterException
-    {
-        for (final Cache.Entry<? extends K, ? extends V> entry : entries)
-        {
-            write(entry);
-        }
-    }
-
-    @Override
-    public void deleteAll(final Collection<?> keys) throws CacheWriterException
-    {
-        for (final Object k : keys)
-        {
-            delete(k);
-        }
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/Statistics.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/Statistics.java
deleted file mode 100644
index 61ec0e9..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/Statistics.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache;
-
-import java.util.concurrent.atomic.AtomicLong;
-
-public class Statistics
-{
-    private volatile boolean active = true;
-
-    private final AtomicLong removals = new AtomicLong();
-    private final AtomicLong expiries = new AtomicLong();
-    private final AtomicLong puts = new AtomicLong();
-    private final AtomicLong hits = new AtomicLong();
-    private final AtomicLong misses = new AtomicLong();
-    private final AtomicLong evictions = new AtomicLong();
-    private final AtomicLong putTimeTaken = new AtomicLong();
-    private final AtomicLong getTimeTaken = new AtomicLong();
-    private final AtomicLong removeTimeTaken = new AtomicLong();
-
-    public long getHits()
-    {
-        return hits.get();
-    }
-
-    public long getMisses()
-    {
-        return misses.get();
-    }
-
-    public long getPuts()
-    {
-        return puts.get();
-    }
-
-    public long getRemovals()
-    {
-        return removals.get();
-    }
-
-    public long getEvictions()
-    {
-        return evictions.get();
-    }
-
-    public long getTimeTakenForGets()
-    {
-        return getTimeTaken.get();
-    }
-
-    public long getTimeTakenForPuts()
-    {
-        return putTimeTaken.get();
-    }
-
-    public long getTimeTakenForRemovals()
-    {
-        return removeTimeTaken.get();
-    }
-
-    public void increaseRemovals(final long number)
-    {
-        increment(removals, number);
-    }
-
-    public void increaseExpiries(final long number)
-    {
-        increment(expiries, number);
-    }
-
-    public void increasePuts(final long number)
-    {
-        increment(puts, number);
-    }
-
-    public void increaseHits(final long number)
-    {
-        increment(hits, number);
-    }
-
-    public void increaseMisses(final long number)
-    {
-        increment(misses, number);
-    }
-
-    public void increaseEvictions(final long number)
-    {
-        increment(evictions, number);
-    }
-
-    public void addGetTime(final long duration)
-    {
-        increment(duration, getTimeTaken);
-    }
-
-    public void addPutTime(final long duration)
-    {
-        increment(duration, putTimeTaken);
-    }
-
-    public void addRemoveTime(final long duration)
-    {
-        increment(duration, removeTimeTaken);
-    }
-
-    private void increment(final AtomicLong counter, final long number)
-    {
-        if (!active)
-        {
-            return;
-        }
-        counter.addAndGet(number);
-    }
-
-    private void increment(final long duration, final AtomicLong counter)
-    {
-        if (!active)
-        {
-            return;
-        }
-
-        if (counter.get() < Long.MAX_VALUE - duration)
-        {
-            counter.addAndGet(duration);
-        }
-        else
-        {
-            reset();
-            counter.set(duration);
-        }
-    }
-
-    public void reset()
-    {
-        puts.set(0);
-        misses.set(0);
-        removals.set(0);
-        expiries.set(0);
-        hits.set(0);
-        evictions.set(0);
-        getTimeTaken.set(0);
-        putTimeTaken.set(0);
-        removeTimeTaken.set(0);
-    }
-
-    public void setActive(final boolean active)
-    {
-        this.active = active;
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/TempStateCacheView.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/TempStateCacheView.java
deleted file mode 100644
index 0b8db82..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/TempStateCacheView.java
+++ /dev/null
@@ -1,331 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache;
-
-import javax.cache.Cache;
-import javax.cache.CacheManager;
-import javax.cache.configuration.CacheEntryListenerConfiguration;
-import javax.cache.configuration.CompleteConfiguration;
-import javax.cache.configuration.Configuration;
-import javax.cache.integration.CompletionListener;
-import javax.cache.processor.EntryProcessor;
-import javax.cache.processor.EntryProcessorException;
-import javax.cache.processor.EntryProcessorResult;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.Map;
-import java.util.Set;
-
-import static org.apache.commons.jcs.jcache.Asserts.assertNotNull;
-
-// kind of transactional view for a Cache<K, V>, to use with EntryProcessor
-public class TempStateCacheView<K, V> implements Cache<K, V>
-{
-    private final JCSCache<K, V> cache;
-    private final Map<K, V> put = new HashMap<K, V>();
-    private final Collection<K> remove = new LinkedList<K>();
-    private boolean removeAll = false;
-    private boolean clear = false;
-
-    public TempStateCacheView(final JCSCache<K, V> entries)
-    {
-        this.cache = entries;
-    }
-
-    public V get(final K key)
-    {
-        if (ignoreKey(key))
-        {
-            return null;
-        }
-
-        final V v = put.get(key);
-        if (v != null)
-        {
-            return v;
-        }
-
-        // for an EntryProcessor we already incremented stats - to enhance
-        // surely
-        if (cache.getConfiguration(CompleteConfiguration.class).isStatisticsEnabled())
-        {
-            final Statistics statistics = cache.getStatistics();
-            if (cache.containsKey(key))
-            {
-                statistics.increaseHits(-1);
-            }
-            else
-            {
-                statistics.increaseMisses(-1);
-            }
-        }
-        return cache.get(key);
-    }
-
-    private boolean ignoreKey(final K key)
-    {
-        return removeAll || clear || remove.contains(key);
-    }
-
-    public Map<K, V> getAll(final Set<? extends K> keys)
-    {
-        final Map<K, V> v = new HashMap<K, V>(keys.size());
-        final Set<K> missing = new HashSet<K>();
-        for (final K k : keys)
-        {
-            final V value = put.get(k);
-            if (value != null)
-            {
-                v.put(k, value);
-            }
-            else if (!ignoreKey(k))
-            {
-                missing.add(k);
-            }
-        }
-        if (!missing.isEmpty())
-        {
-            v.putAll(cache.getAll(missing));
-        }
-        return v;
-    }
-
-    public boolean containsKey(final K key)
-    {
-        return !ignoreKey(key) && (put.containsKey(key) || cache.containsKey(key));
-    }
-
-    public void loadAll(final Set<? extends K> keys, final boolean replaceExistingValues, final CompletionListener completionListener)
-    {
-        cache.loadAll(keys, replaceExistingValues, completionListener);
-    }
-
-    public void put(final K key, final V value)
-    {
-        assertNotNull(key, "key");
-        assertNotNull(value, "value");
-        put.put(key, value);
-        remove.remove(key);
-    }
-
-    public V getAndPut(final K key, final V value)
-    {
-        final V v = get(key);
-        put(key, value);
-        return v;
-    }
-
-    public void putAll(final Map<? extends K, ? extends V> map)
-    {
-        put.putAll(map);
-        for (final K k : map.keySet())
-        {
-            remove.remove(k);
-        }
-    }
-
-    public boolean putIfAbsent(final K key, final V value)
-    {
-        if (!put.containsKey(key))
-        {
-            put.put(key, value);
-            remove.remove(key);
-            return true;
-        }
-        return false;
-    }
-
-    public boolean remove(final K key)
-    {
-        final boolean noop = put.containsKey(key);
-        put.remove(key);
-        if (!ignoreKey(key))
-        {
-            if (!noop)
-            {
-                remove.add(key);
-            }
-            return true;
-        }
-        return false;
-    }
-
-    public boolean remove(final K key, final V oldValue)
-    {
-        put.remove(key);
-        if (!ignoreKey(key) && oldValue.equals(cache.get(key)))
-        {
-            remove.add(key);
-            return true;
-        }
-        return false;
-    }
-
-    public V getAndRemove(final K key)
-    {
-        final V v = get(key);
-        remove.add(key);
-        put.remove(key);
-        return v;
-    }
-
-    public boolean replace(final K key, final V oldValue, final V newValue)
-    {
-        if (oldValue.equals(get(key)))
-        {
-            put(key, newValue);
-            return true;
-        }
-        return false;
-    }
-
-    public boolean replace(final K key, final V value)
-    {
-        if (containsKey(key))
-        {
-            remove(key);
-            return true;
-        }
-        return false;
-    }
-
-    public V getAndReplace(final K key, final V value)
-    {
-        if (containsKey(key))
-        {
-            final V oldValue = get(key);
-            put(key, value);
-            return oldValue;
-        }
-        return null;
-    }
-
-    public void removeAll(final Set<? extends K> keys)
-    {
-        remove.addAll(keys);
-        for (final K k : keys)
-        {
-            put.remove(k);
-        }
-    }
-
-    @Override
-    public void removeAll()
-    {
-        removeAll = true;
-        put.clear();
-        remove.clear();
-    }
-
-    @Override
-    public void clear()
-    {
-        clear = true;
-        put.clear();
-        remove.clear();
-    }
-
-    public <C extends Configuration<K, V>> C getConfiguration(final Class<C> clazz)
-    {
-        return cache.getConfiguration(clazz);
-    }
-
-    public <T> T invoke(final K key, final EntryProcessor<K, V, T> entryProcessor, final Object... arguments) throws EntryProcessorException
-    {
-        return cache.invoke(key, entryProcessor, arguments);
-    }
-
-    public <T> Map<K, EntryProcessorResult<T>> invokeAll(Set<? extends K> keys, final EntryProcessor<K, V, T> entryProcessor,
-            final Object... arguments)
-    {
-        return cache.invokeAll(keys, entryProcessor, arguments);
-    }
-
-    @Override
-    public String getName()
-    {
-        return cache.getName();
-    }
-
-    @Override
-    public CacheManager getCacheManager()
-    {
-        return cache.getCacheManager();
-    }
-
-    @Override
-    public void close()
-    {
-        cache.close();
-    }
-
-    @Override
-    public boolean isClosed()
-    {
-        return cache.isClosed();
-    }
-
-    @Override
-    public <T> T unwrap(final Class<T> clazz)
-    {
-        return cache.unwrap(clazz);
-    }
-
-    public void registerCacheEntryListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration)
-    {
-        cache.registerCacheEntryListener(cacheEntryListenerConfiguration);
-    }
-
-    public void deregisterCacheEntryListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration)
-    {
-        cache.deregisterCacheEntryListener(cacheEntryListenerConfiguration);
-    }
-
-    @Override
-    public Iterator<Entry<K, V>> iterator()
-    {
-        return cache.iterator();
-    }
-
-    public void merge()
-    {
-        if (removeAll)
-        {
-            cache.removeAll();
-        }
-        if (clear)
-        {
-            cache.clear();
-        }
-
-        for (final Map.Entry<K, V> entry : put.entrySet())
-        {
-            cache.put(entry.getKey(), entry.getValue());
-        }
-        put.clear();
-        for (final K entry : remove)
-        {
-            cache.remove(entry);
-        }
-        remove.clear();
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/Times.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/Times.java
deleted file mode 100644
index 66cd992..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/Times.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package org.apache.commons.jcs.jcache;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-public class Times
-{
-    public static long now(final boolean ignore)
-    {
-        if (ignore)
-        {
-            return -1;
-        }
-        return System.nanoTime() / 1000;
-    }
-
-    private Times()
-    {
-        // no-op
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CDIJCacheHelper.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CDIJCacheHelper.java
deleted file mode 100644
index 4ec5731..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CDIJCacheHelper.java
+++ /dev/null
@@ -1,670 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.cdi;
-
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.logging.Logger;
-
-import javax.annotation.PreDestroy;
-import javax.cache.annotation.CacheDefaults;
-import javax.cache.annotation.CacheKey;
-import javax.cache.annotation.CacheKeyGenerator;
-import javax.cache.annotation.CachePut;
-import javax.cache.annotation.CacheRemove;
-import javax.cache.annotation.CacheRemoveAll;
-import javax.cache.annotation.CacheResolverFactory;
-import javax.cache.annotation.CacheResult;
-import javax.cache.annotation.CacheValue;
-import javax.enterprise.context.ApplicationScoped;
-import javax.enterprise.context.spi.CreationalContext;
-import javax.enterprise.inject.spi.Bean;
-import javax.enterprise.inject.spi.BeanManager;
-import javax.inject.Inject;
-import javax.interceptor.InvocationContext;
-
-@ApplicationScoped
-public class CDIJCacheHelper
-{
-    private static final Logger LOGGER = Logger.getLogger(CDIJCacheHelper.class.getName());
-    private static final boolean CLOSE_CACHE = !Boolean.getBoolean("org.apache.commons.jcs.jcache.cdi.skip-close");
-
-    private volatile CacheResolverFactoryImpl defaultCacheResolverFactory = null; // lazy to not create any cache if not needed
-    private final CacheKeyGeneratorImpl defaultCacheKeyGenerator = new CacheKeyGeneratorImpl();
-
-    private final Collection<CreationalContext<?>> toRelease = new ArrayList<CreationalContext<?>>();
-    private final ConcurrentMap<MethodKey, MethodMeta> methods = new ConcurrentHashMap<MethodKey, MethodMeta>();
-
-    @Inject
-    private BeanManager beanManager;
-
-    @PreDestroy
-    private void release() {
-        if (CLOSE_CACHE && defaultCacheResolverFactory != null)
-        {
-            defaultCacheResolverFactory.release();
-        }
-        for (final CreationalContext<?> cc : toRelease)
-        {
-            try
-            {
-                cc.release();
-            }
-            catch (final RuntimeException re)
-            {
-                LOGGER.warning(re.getMessage());
-            }
-        }
-    }
-
-    public MethodMeta findMeta(final InvocationContext ic)
-    {
-        final Method mtd = ic.getMethod();
-        final Class<?> refType = findKeyType(ic.getTarget());
-        final MethodKey key = new MethodKey(refType, mtd);
-        MethodMeta methodMeta = methods.get(key);
-        if (methodMeta == null)
-        {
-            synchronized (this)
-            {
-                methodMeta = methods.get(key);
-                if (methodMeta == null)
-                {
-                    methodMeta = createMeta(ic);
-                    methods.put(key, methodMeta);
-                }
-            }
-        }
-        return methodMeta;
-    }
-
-    private Class<?> findKeyType(final Object target)
-    {
-        if (null == target)
-        {
-            return null;
-        }
-        return target.getClass();
-    }
-
-    // it is unlikely we have all annotations but for now we have a single meta model
-    private MethodMeta createMeta(final InvocationContext ic)
-    {
-        final CacheDefaults defaults = findDefaults(ic.getTarget() == null ? null : ic.getTarget()
-                                                      .getClass(), ic.getMethod());
-
-        final Class<?>[] parameterTypes = ic.getMethod().getParameterTypes();
-        final Annotation[][] parameterAnnotations = ic.getMethod().getParameterAnnotations();
-        final List<Set<Annotation>> annotations = new ArrayList<Set<Annotation>>();
-        for (final Annotation[] parameterAnnotation : parameterAnnotations)
-        {
-            final Set<Annotation> set = new HashSet<Annotation>(parameterAnnotation.length);
-            set.addAll(Arrays.asList(parameterAnnotation));
-            annotations.add(set);
-        }
-
-        final Set<Annotation> mtdAnnotations = new HashSet<Annotation>();
-        mtdAnnotations.addAll(Arrays.asList(ic.getMethod().getAnnotations()));
-
-        final CacheResult cacheResult = ic.getMethod().getAnnotation(CacheResult.class);
-        final String cacheResultCacheResultName = cacheResult == null ? null : defaultName(ic.getMethod(), defaults, cacheResult.cacheName());
-        final CacheResolverFactory cacheResultCacheResolverFactory = cacheResult == null ?
-                null : cacheResolverFactoryFor(defaults, cacheResult.cacheResolverFactory());
-        final CacheKeyGenerator cacheResultCacheKeyGenerator = cacheResult == null ?
-                null : cacheKeyGeneratorFor(defaults, cacheResult.cacheKeyGenerator());
-
-        final CachePut cachePut = ic.getMethod().getAnnotation(CachePut.class);
-        final String cachePutCachePutName = cachePut == null ? null : defaultName(ic.getMethod(), defaults, cachePut.cacheName());
-        final CacheResolverFactory cachePutCacheResolverFactory = cachePut == null ?
-                null : cacheResolverFactoryFor(defaults, cachePut.cacheResolverFactory());
-        final CacheKeyGenerator cachePutCacheKeyGenerator = cachePut == null ?
-                null : cacheKeyGeneratorFor(defaults, cachePut.cacheKeyGenerator());
-
-        final CacheRemove cacheRemove = ic.getMethod().getAnnotation(CacheRemove.class);
-        final String cacheRemoveCacheRemoveName = cacheRemove == null ? null : defaultName(ic.getMethod(), defaults, cacheRemove.cacheName());
-        final CacheResolverFactory cacheRemoveCacheResolverFactory = cacheRemove == null ?
-                null : cacheResolverFactoryFor(defaults, cacheRemove.cacheResolverFactory());
-        final CacheKeyGenerator cacheRemoveCacheKeyGenerator = cacheRemove == null ?
-                null : cacheKeyGeneratorFor(defaults, cacheRemove.cacheKeyGenerator());
-
-        final CacheRemoveAll cacheRemoveAll = ic.getMethod().getAnnotation(CacheRemoveAll.class);
-        final String cacheRemoveAllCacheRemoveAllName = cacheRemoveAll == null ? null : defaultName(ic.getMethod(), defaults, cacheRemoveAll.cacheName());
-        final CacheResolverFactory cacheRemoveAllCacheResolverFactory = cacheRemoveAll == null ?
-                null : cacheResolverFactoryFor(defaults, cacheRemoveAll.cacheResolverFactory());
-
-        return new MethodMeta(
-                parameterTypes,
-                annotations,
-                mtdAnnotations,
-                keyParameterIndexes(ic.getMethod()),
-                getValueParameter(annotations),
-                getKeyParameters(annotations),
-                cacheResultCacheResultName,
-                cacheResultCacheResolverFactory,
-                cacheResultCacheKeyGenerator,
-                cacheResult,
-                cachePutCachePutName,
-                cachePutCacheResolverFactory,
-                cachePutCacheKeyGenerator,
-                cachePut != null && cachePut.afterInvocation(),
-                cachePut,
-                cacheRemoveCacheRemoveName,
-                cacheRemoveCacheResolverFactory,
-                cacheRemoveCacheKeyGenerator,
-                cacheRemove != null && cacheRemove.afterInvocation(),
-                cacheRemove,
-                cacheRemoveAllCacheRemoveAllName,
-                cacheRemoveAllCacheResolverFactory,
-                cacheRemoveAll != null && cacheRemoveAll.afterInvocation(),
-                cacheRemoveAll);
-    }
-
-    private Integer[] getKeyParameters(final List<Set<Annotation>> annotations)
-    {
-        final Collection<Integer> list = new ArrayList<Integer>();
-        int idx = 0;
-        for (final Set<Annotation> set : annotations)
-        {
-            for (final Annotation a : set)
-            {
-                if (a.annotationType() == CacheKey.class)
-                {
-                    list.add(idx);
-                }
-            }
-            idx++;
-        }
-        if (list.isEmpty())
-        {
-            for (int i = 0; i < annotations.size(); i++)
-            {
-                list.add(i);
-            }
-        }
-        return list.toArray(new Integer[list.size()]);
-    }
-
-    private Integer getValueParameter(final List<Set<Annotation>> annotations)
-    {
-        int idx = 0;
-        for (final Set<Annotation> set : annotations)
-        {
-            for (final Annotation a : set)
-            {
-                if (a.annotationType() == CacheValue.class)
-                {
-                    return idx;
-                }
-            }
-        }
-        return -1;
-    }
-
-    private String defaultName(final Method method, final CacheDefaults defaults, final String cacheName)
-    {
-        if (!cacheName.isEmpty())
-        {
-            return cacheName;
-        }
-        if (defaults != null)
-        {
-            final String name = defaults.cacheName();
-            if (!name.isEmpty())
-            {
-                return name;
-            }
-        }
-
-        final StringBuilder name = new StringBuilder(method.getDeclaringClass().getName());
-        name.append(".");
-        name.append(method.getName());
-        name.append("(");
-        final Class<?>[] parameterTypes = method.getParameterTypes();
-        for (int pIdx = 0; pIdx < parameterTypes.length; pIdx++)
-        {
-            name.append(parameterTypes[pIdx].getName());
-            if ((pIdx + 1) < parameterTypes.length)
-            {
-                name.append(",");
-            }
-        }
-        name.append(")");
-        return name.toString();
-    }
-
-    private CacheDefaults findDefaults(final Class<?> targetType, final Method method)
-    {
-        if (Proxy.isProxyClass(targetType)) // target doesnt hold annotations
-        {
-            final Class<?> api = method.getDeclaringClass();
-            for (final Class<?> type : targetType
-                                         .getInterfaces())
-            {
-                if (!api.isAssignableFrom(type))
-                {
-                    continue;
-                }
-                return extractDefaults(type);
-            }
-        }
-        return extractDefaults(targetType);
-    }
-
-    private CacheDefaults extractDefaults(final Class<?> type)
-    {
-        CacheDefaults annotation = null;
-        Class<?> clazz = type;
-        while (clazz != null && clazz != Object.class)
-        {
-            annotation = clazz.getAnnotation(CacheDefaults.class);
-            if (annotation != null)
-            {
-                break;
-            }
-            clazz = clazz.getSuperclass();
-        }
-        return annotation;
-    }
-
-    public boolean isIncluded(final Class<?> aClass, final Class<?>[] in, final Class<?>[] out)
-    {
-        if (in.length == 0 && out.length == 0)
-        {
-            return false;
-        }
-        for (final Class<?> potentialIn : in)
-        {
-            if (potentialIn.isAssignableFrom(aClass))
-            {
-                for (final Class<?> potentialOut : out)
-                {
-                    if (potentialOut.isAssignableFrom(aClass))
-                    {
-                        return false;
-                    }
-                }
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private CacheKeyGenerator cacheKeyGeneratorFor(final CacheDefaults defaults, final Class<? extends CacheKeyGenerator> cacheKeyGenerator)
-    {
-        if (!CacheKeyGenerator.class.equals(cacheKeyGenerator))
-        {
-            return instance(cacheKeyGenerator);
-        }
-        if (defaults != null)
-        {
-            final Class<? extends CacheKeyGenerator> defaultCacheKeyGenerator = defaults.cacheKeyGenerator();
-            if (!CacheKeyGenerator.class.equals(defaultCacheKeyGenerator))
-            {
-                return instance(defaultCacheKeyGenerator);
-            }
-        }
-        return defaultCacheKeyGenerator;
-    }
-
-    private CacheResolverFactory cacheResolverFactoryFor(final CacheDefaults defaults, final Class<? extends CacheResolverFactory> cacheResolverFactory)
-    {
-        if (!CacheResolverFactory.class.equals(cacheResolverFactory))
-        {
-            return instance(cacheResolverFactory);
-        }
-        if (defaults != null)
-        {
-            final Class<? extends CacheResolverFactory> defaultCacheResolverFactory = defaults.cacheResolverFactory();
-            if (!CacheResolverFactory.class.equals(defaultCacheResolverFactory))
-            {
-                return instance(defaultCacheResolverFactory);
-            }
-        }
-        return defaultCacheResolverFactory();
-    }
-
-    private <T> T instance(final Class<T> type)
-    {
-        final Set<Bean<?>> beans = beanManager.getBeans(type);
-        if (beans.isEmpty())
-        {
-            if (CacheKeyGenerator.class == type) {
-                return (T) defaultCacheKeyGenerator;
-            }
-            if (CacheResolverFactory.class == type) {
-                return (T) defaultCacheResolverFactory();
-            }
-            return null;
-        }
-        final Bean<?> bean = beanManager.resolve(beans);
-        final CreationalContext<?> context = beanManager.createCreationalContext(bean);
-        final Class<? extends Annotation> scope = bean.getScope();
-        final boolean normalScope = beanManager.isNormalScope(scope);
-        try
-        {
-            final Object reference = beanManager.getReference(bean, bean.getBeanClass(), context);
-            if (!normalScope)
-            {
-                toRelease.add(context);
-            }
-            return (T) reference;
-        }
-        finally
-        {
-            if (normalScope)
-            { // TODO: release at the right moment, @PreDestroy? question is: do we assume it is thread safe?
-                context.release();
-            }
-        }
-    }
-
-    private CacheResolverFactoryImpl defaultCacheResolverFactory()
-    {
-        if (defaultCacheResolverFactory != null) {
-            return defaultCacheResolverFactory;
-        }
-        synchronized (this) {
-            if (defaultCacheResolverFactory != null) {
-                return defaultCacheResolverFactory;
-            }
-            defaultCacheResolverFactory = new CacheResolverFactoryImpl();
-        }
-        return defaultCacheResolverFactory;
-    }
-
-    private Integer[] keyParameterIndexes(final Method method)
-    {
-        final List<Integer> keys = new LinkedList<Integer>();
-        final Annotation[][] parameterAnnotations = method.getParameterAnnotations();
-
-        // first check if keys are specified explicitely
-        for (int i = 0; i < method.getParameterTypes().length; i++)
-        {
-            final Annotation[] annotations = parameterAnnotations[i];
-            for (final Annotation a : annotations)
-            {
-                if (a.annotationType().equals(CacheKey.class))
-                {
-                    keys.add(i);
-                    break;
-                }
-            }
-        }
-
-        // if not then use all parameters but value ones
-        if (keys.isEmpty())
-        {
-            for (int i = 0; i < method.getParameterTypes().length; i++)
-            {
-                final Annotation[] annotations = parameterAnnotations[i];
-                boolean value = false;
-                for (final Annotation a : annotations)
-                {
-                    if (a.annotationType().equals(CacheValue.class))
-                    {
-                        value = true;
-                        break;
-                    }
-                }
-                if (!value) {
-                    keys.add(i);
-                }
-            }
-        }
-        return keys.toArray(new Integer[keys.size()]);
-    }
-
-    private static final class MethodKey
-    {
-        private final Class<?> base;
-        private final Method delegate;
-        private final int hash;
-
-        private MethodKey(final Class<?> base, final Method delegate)
-        {
-            this.base = base; // we need a class to ensure inheritance don't fall in the same key
-            this.delegate = delegate;
-            this.hash = 31 * delegate.hashCode() + (base == null ? 0 : base.hashCode());
-        }
-
-        @Override
-        public boolean equals(final Object o)
-        {
-            if (this == o)
-            {
-                return true;
-            }
-            if (o == null || getClass() != o.getClass())
-            {
-                return false;
-            }
-            final MethodKey classKey = MethodKey.class.cast(o);
-            return delegate.equals(classKey.delegate) && ((base == null && classKey.base == null) || (base != null && base.equals(classKey.base)));
-        }
-
-        @Override
-        public int hashCode()
-        {
-            return hash;
-        }
-    }
-
-    // TODO: split it in 5?
-    public static class MethodMeta
-    {
-        private final Class<?>[] parameterTypes;
-        private final List<Set<Annotation>> parameterAnnotations;
-        private final Set<Annotation> annotations;
-        private final Integer[] keysIndices;
-        private final Integer valueIndex;
-        private final Integer[] parameterIndices;
-
-        private final String cacheResultCacheName;
-        private final CacheResolverFactory cacheResultResolverFactory;
-        private final CacheKeyGenerator cacheResultKeyGenerator;
-        private final CacheResult cacheResult;
-
-        private final String cachePutCacheName;
-        private final CacheResolverFactory cachePutResolverFactory;
-        private final CacheKeyGenerator cachePutKeyGenerator;
-        private final boolean cachePutAfter;
-        private final CachePut cachePut;
-
-        private final String cacheRemoveCacheName;
-        private final CacheResolverFactory cacheRemoveResolverFactory;
-        private final CacheKeyGenerator cacheRemoveKeyGenerator;
-        private final boolean cacheRemoveAfter;
-        private final CacheRemove cacheRemove;
-
-        private final String cacheRemoveAllCacheName;
-        private final CacheResolverFactory cacheRemoveAllResolverFactory;
-        private final boolean cacheRemoveAllAfter;
-        private final CacheRemoveAll cacheRemoveAll;
-
-        public MethodMeta(Class<?>[] parameterTypes, List<Set<Annotation>> parameterAnnotations, Set<Annotation> 
-                annotations, Integer[] keysIndices, Integer valueIndex, Integer[] parameterIndices, String 
-                cacheResultCacheName, CacheResolverFactory cacheResultResolverFactory, CacheKeyGenerator 
-                cacheResultKeyGenerator, CacheResult cacheResult, String cachePutCacheName, CacheResolverFactory 
-                cachePutResolverFactory, CacheKeyGenerator cachePutKeyGenerator, boolean cachePutAfter, CachePut cachePut, String
-                cacheRemoveCacheName, CacheResolverFactory cacheRemoveResolverFactory, CacheKeyGenerator 
-                cacheRemoveKeyGenerator, boolean cacheRemoveAfter, CacheRemove cacheRemove, String cacheRemoveAllCacheName,
-                          CacheResolverFactory cacheRemoveAllResolverFactory, boolean
-                                  cacheRemoveAllAfter, CacheRemoveAll cacheRemoveAll)
-        {
-            this.parameterTypes = parameterTypes;
-            this.parameterAnnotations = parameterAnnotations;
-            this.annotations = annotations;
-            this.keysIndices = keysIndices;
-            this.valueIndex = valueIndex;
-            this.parameterIndices = parameterIndices;
-            this.cacheResultCacheName = cacheResultCacheName;
-            this.cacheResultResolverFactory = cacheResultResolverFactory;
-            this.cacheResultKeyGenerator = cacheResultKeyGenerator;
-            this.cacheResult = cacheResult;
-            this.cachePutCacheName = cachePutCacheName;
-            this.cachePutResolverFactory = cachePutResolverFactory;
-            this.cachePutKeyGenerator = cachePutKeyGenerator;
-            this.cachePutAfter = cachePutAfter;
-            this.cachePut = cachePut;
-            this.cacheRemoveCacheName = cacheRemoveCacheName;
-            this.cacheRemoveResolverFactory = cacheRemoveResolverFactory;
-            this.cacheRemoveKeyGenerator = cacheRemoveKeyGenerator;
-            this.cacheRemoveAfter = cacheRemoveAfter;
-            this.cacheRemove = cacheRemove;
-            this.cacheRemoveAllCacheName = cacheRemoveAllCacheName;
-            this.cacheRemoveAllResolverFactory = cacheRemoveAllResolverFactory;
-            this.cacheRemoveAllAfter = cacheRemoveAllAfter;
-            this.cacheRemoveAll = cacheRemoveAll;
-        }
-
-        public boolean isCacheRemoveAfter()
-        {
-            return cacheRemoveAfter;
-        }
-
-        public boolean isCachePutAfter()
-        {
-            return cachePutAfter;
-        }
-
-        public Class<?>[] getParameterTypes()
-        {
-            return parameterTypes;
-        }
-
-        public List<Set<Annotation>> getParameterAnnotations()
-        {
-            return parameterAnnotations;
-        }
-
-        public String getCacheResultCacheName()
-        {
-            return cacheResultCacheName;
-        }
-
-        public CacheResolverFactory getCacheResultResolverFactory()
-        {
-            return cacheResultResolverFactory;
-        }
-
-        public CacheKeyGenerator getCacheResultKeyGenerator()
-        {
-            return cacheResultKeyGenerator;
-        }
-
-        public CacheResult getCacheResult() {
-            return cacheResult;
-        }
-
-        public Integer[] getParameterIndices()
-        {
-            return parameterIndices;
-        }
-
-        public Set<Annotation> getAnnotations()
-        {
-            return annotations;
-        }
-
-        public Integer[] getKeysIndices()
-        {
-            return keysIndices;
-        }
-
-        public Integer getValuesIndex()
-        {
-            return valueIndex;
-        }
-
-        public Integer getValueIndex()
-        {
-            return valueIndex;
-        }
-
-        public String getCachePutCacheName()
-        {
-            return cachePutCacheName;
-        }
-
-        public CacheResolverFactory getCachePutResolverFactory()
-        {
-            return cachePutResolverFactory;
-        }
-
-        public CacheKeyGenerator getCachePutKeyGenerator()
-        {
-            return cachePutKeyGenerator;
-        }
-
-        public CachePut getCachePut()
-        {
-            return cachePut;
-        }
-
-        public String getCacheRemoveCacheName()
-        {
-            return cacheRemoveCacheName;
-        }
-
-        public CacheResolverFactory getCacheRemoveResolverFactory()
-        {
-            return cacheRemoveResolverFactory;
-        }
-
-        public CacheKeyGenerator getCacheRemoveKeyGenerator()
-        {
-            return cacheRemoveKeyGenerator;
-        }
-
-        public CacheRemove getCacheRemove()
-        {
-            return cacheRemove;
-        }
-
-        public String getCacheRemoveAllCacheName()
-        {
-            return cacheRemoveAllCacheName;
-        }
-
-        public CacheResolverFactory getCacheRemoveAllResolverFactory()
-        {
-            return cacheRemoveAllResolverFactory;
-        }
-
-        public boolean isCacheRemoveAllAfter()
-        {
-            return cacheRemoveAllAfter;
-        }
-
-        public CacheRemoveAll getCacheRemoveAll()
-        {
-            return cacheRemoveAll;
-        }
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CacheInvocationContextImpl.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CacheInvocationContextImpl.java
deleted file mode 100644
index c013a73..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CacheInvocationContextImpl.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.cdi;
-
-import java.lang.annotation.Annotation;
-import java.util.List;
-import java.util.Set;
-
-import javax.cache.annotation.CacheInvocationContext;
-import javax.cache.annotation.CacheInvocationParameter;
-import javax.interceptor.InvocationContext;
-
-public class CacheInvocationContextImpl<A extends Annotation> extends CacheMethodDetailsImpl<A> implements CacheInvocationContext<A>
-{
-    private static final Object[] EMPTY_ARGS = new Object[0];
-
-    private CacheInvocationParameter[] parameters = null;
-
-    public CacheInvocationContextImpl(final InvocationContext delegate, final A cacheAnnotation, final String cacheName,
-                                      final CDIJCacheHelper.MethodMeta meta)
-    {
-        super(delegate, cacheAnnotation, cacheName, meta);
-    }
-
-    @Override
-    public Object getTarget()
-    {
-        return delegate.getTarget();
-    }
-
-    @Override
-    public CacheInvocationParameter[] getAllParameters()
-    {
-        if (parameters == null)
-        {
-            parameters = doGetAllParameters(null);
-        }
-        return parameters;
-    }
-
-    @Override
-    public <T> T unwrap(final Class<T> cls)
-    {
-        if (cls.isAssignableFrom(getClass()))
-        {
-            return cls.cast(this);
-        }
-        throw new IllegalArgumentException(cls.getName());
-    }
-
-    protected CacheInvocationParameter[] doGetAllParameters(final Integer[] indexes)
-    {
-        final Object[] parameters = delegate.getParameters();
-        final Object[] args = parameters == null ? EMPTY_ARGS : parameters;
-        final Class<?>[] parameterTypes = meta.getParameterTypes();
-        final List<Set<Annotation>> parameterAnnotations = meta.getParameterAnnotations();
-
-        final CacheInvocationParameter[] parametersAsArray = new CacheInvocationParameter[indexes == null ? args.length : indexes.length];
-        if (indexes == null)
-        {
-            for (int i = 0; i < args.length; i++)
-            {
-                parametersAsArray[i] = newCacheInvocationParameterImpl(parameterTypes[i], args[i], parameterAnnotations.get(i), i);
-            }
-        }
-        else
-        {
-            int outIdx = 0;
-            for (int idx = 0; idx < indexes.length; idx++)
-            {
-                final int i = indexes[idx];
-                parametersAsArray[outIdx] = newCacheInvocationParameterImpl(parameterTypes[i], args[i], parameterAnnotations.get(i), i);
-                outIdx++;
-            }
-        }
-        return parametersAsArray;
-    }
-
-    private CacheInvocationParameterImpl newCacheInvocationParameterImpl(final Class<?> type, final Object arg,
-                                                                         final Set<Annotation> annotations, final int i) {
-        return new CacheInvocationParameterImpl(type, arg, annotations, i);
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CacheInvocationParameterImpl.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CacheInvocationParameterImpl.java
deleted file mode 100644
index 9944d4b..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CacheInvocationParameterImpl.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.cdi;
-
-import javax.cache.annotation.CacheInvocationParameter;
-import java.lang.annotation.Annotation;
-import java.util.Set;
-
-public class CacheInvocationParameterImpl implements CacheInvocationParameter
-{
-    private final Class<?> type;
-    private final Object value;
-    private final Set<Annotation> annotations;
-    private final int position;
-
-    public CacheInvocationParameterImpl(final Class<?> type, final Object value, final Set<Annotation> annotations, final int position)
-    {
-        this.type = type;
-        this.value = value;
-        this.annotations = annotations;
-        this.position = position;
-    }
-
-    @Override
-    public Class<?> getRawType()
-    {
-        return type;
-    }
-
-    @Override
-    public Object getValue()
-    {
-        return value;
-    }
-
-    @Override
-    public Set<Annotation> getAnnotations()
-    {
-        return annotations;
-    }
-
-    @Override
-    public int getParameterPosition()
-    {
-        return position;
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CacheKeyGeneratorImpl.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CacheKeyGeneratorImpl.java
deleted file mode 100644
index 06eaa4b..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CacheKeyGeneratorImpl.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.cdi;
-
-import javax.cache.annotation.CacheInvocationParameter;
-import javax.cache.annotation.CacheKeyGenerator;
-import javax.cache.annotation.CacheKeyInvocationContext;
-import javax.cache.annotation.GeneratedCacheKey;
-import java.lang.annotation.Annotation;
-
-public class CacheKeyGeneratorImpl implements CacheKeyGenerator
-{
-    @Override
-    public GeneratedCacheKey generateCacheKey(final CacheKeyInvocationContext<? extends Annotation> cacheKeyInvocationContext)
-    {
-        final CacheInvocationParameter[] keyParameters = cacheKeyInvocationContext.getKeyParameters();
-        final Object[] parameters = new Object[keyParameters.length];
-        for (int index = 0; index < keyParameters.length; index++)
-        {
-            parameters[index] = keyParameters[index].getValue();
-        }
-        return new GeneratedCacheKeyImpl(parameters);
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CacheKeyInvocationContextImpl.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CacheKeyInvocationContextImpl.java
deleted file mode 100644
index 6d87d26..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CacheKeyInvocationContextImpl.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.cdi;
-
-import java.lang.annotation.Annotation;
-
-import javax.cache.annotation.CacheInvocationParameter;
-import javax.cache.annotation.CacheKeyInvocationContext;
-import javax.interceptor.InvocationContext;
-
-public class CacheKeyInvocationContextImpl<A extends Annotation> extends CacheInvocationContextImpl<A> implements CacheKeyInvocationContext<A>
-{
-    private CacheInvocationParameter[] keyParams = null;
-    private CacheInvocationParameter valueParam = null;
-
-    public CacheKeyInvocationContextImpl(final InvocationContext delegate, final A annotation, final String name,
-                                         final CDIJCacheHelper.MethodMeta methodMeta)
-    {
-        super(delegate, annotation, name, methodMeta);
-    }
-
-    @Override
-    public CacheInvocationParameter[] getKeyParameters()
-    {
-        if (keyParams == null)
-        {
-            keyParams = doGetAllParameters(meta.getKeysIndices());
-        }
-        return keyParams;
-    }
-
-    @Override
-    public CacheInvocationParameter getValueParameter()
-    {
-        if (valueParam == null)
-        {
-            valueParam = meta.getValueIndex() >= 0 ? doGetAllParameters(new Integer[]{meta.getValueIndex()})[0] : null;
-        }
-        return valueParam;
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CacheMethodDetailsImpl.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CacheMethodDetailsImpl.java
deleted file mode 100644
index 349b27b..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CacheMethodDetailsImpl.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.cdi;
-
-import javax.cache.annotation.CacheMethodDetails;
-import javax.interceptor.InvocationContext;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
-import java.util.HashSet;
-import java.util.Set;
-
-import static java.util.Arrays.asList;
-
-public class CacheMethodDetailsImpl<A extends Annotation> implements CacheMethodDetails<A>
-{
-    protected final InvocationContext delegate;
-    private final Set<Annotation> annotations;
-    private final A cacheAnnotation;
-    private final String cacheName;
-    protected final CDIJCacheHelper.MethodMeta meta;
-
-    public CacheMethodDetailsImpl(final InvocationContext delegate, final A cacheAnnotation, final String cacheName,
-                                  final CDIJCacheHelper.MethodMeta meta)
-    {
-        this.delegate = delegate;
-        this.annotations = meta.getAnnotations();
-        this.cacheAnnotation = cacheAnnotation;
-        this.cacheName = cacheName;
-        this.meta = meta;
-    }
-
-    @Override
-    public Method getMethod()
-    {
-        return delegate.getMethod();
-    }
-
-    @Override
-    public Set<Annotation> getAnnotations()
-    {
-        return annotations;
-    }
-
-    @Override
-    public A getCacheAnnotation()
-    {
-        return cacheAnnotation;
-    }
-
-    @Override
-    public String getCacheName()
-    {
-        return cacheName;
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CachePutInterceptor.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CachePutInterceptor.java
deleted file mode 100644
index 67b9d40..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CachePutInterceptor.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.cdi;
-
-import java.io.Serializable;
-
-import javax.annotation.Priority;
-import javax.cache.Cache;
-import javax.cache.annotation.CacheKeyInvocationContext;
-import javax.cache.annotation.CachePut;
-import javax.cache.annotation.CacheResolver;
-import javax.cache.annotation.CacheResolverFactory;
-import javax.cache.annotation.GeneratedCacheKey;
-import javax.inject.Inject;
-import javax.interceptor.AroundInvoke;
-import javax.interceptor.Interceptor;
-import javax.interceptor.InvocationContext;
-
-@CachePut
-@Interceptor
-@Priority(/*LIBRARY_BEFORE*/1000)
-public class CachePutInterceptor implements Serializable
-{
-    @Inject
-    private CDIJCacheHelper helper;
-
-    @AroundInvoke
-    public Object cache(final InvocationContext ic) throws Throwable
-    {
-        final CDIJCacheHelper.MethodMeta methodMeta = helper.findMeta(ic);
-
-        final String cacheName = methodMeta.getCachePutCacheName();
-
-        final CacheResolverFactory cacheResolverFactory = methodMeta.getCachePutResolverFactory();
-        final CacheKeyInvocationContext<CachePut> context = new CacheKeyInvocationContextImpl<CachePut>(
-                ic, methodMeta.getCachePut(), cacheName, methodMeta);
-        final CacheResolver cacheResolver = cacheResolverFactory.getCacheResolver(context);
-        final Cache<Object, Object> cache = cacheResolver.resolveCache(context);
-
-        final GeneratedCacheKey cacheKey = methodMeta.getCachePutKeyGenerator().generateCacheKey(context);
-        final CachePut cachePut = methodMeta.getCachePut();
-        final boolean afterInvocation = methodMeta.isCachePutAfter();
-
-        if (!afterInvocation)
-        {
-            cache.put(cacheKey, context.getValueParameter());
-        }
-
-        final Object result;
-        try
-        {
-            result = ic.proceed();
-        }
-        catch (final Throwable t)
-        {
-            if (afterInvocation)
-            {
-                if (helper.isIncluded(t.getClass(), cachePut.cacheFor(), cachePut.noCacheFor()))
-                {
-                    cache.put(cacheKey, context.getValueParameter());
-                }
-            }
-
-            throw t;
-        }
-
-        if (afterInvocation)
-        {
-            cache.put(cacheKey, context.getValueParameter());
-        }
-
-        return result;
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CacheRemoveAllInterceptor.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CacheRemoveAllInterceptor.java
deleted file mode 100644
index a86cab8..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CacheRemoveAllInterceptor.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.cdi;
-
-import java.io.Serializable;
-
-import javax.annotation.Priority;
-import javax.cache.Cache;
-import javax.cache.annotation.CacheKeyInvocationContext;
-import javax.cache.annotation.CacheRemoveAll;
-import javax.cache.annotation.CacheResolver;
-import javax.cache.annotation.CacheResolverFactory;
-import javax.inject.Inject;
-import javax.interceptor.AroundInvoke;
-import javax.interceptor.Interceptor;
-import javax.interceptor.InvocationContext;
-
-@CacheRemoveAll
-@Interceptor
-@Priority(/*LIBRARY_BEFORE*/1000)
-public class CacheRemoveAllInterceptor implements Serializable
-{
-    @Inject
-    private CDIJCacheHelper helper;
-
-    @AroundInvoke
-    public Object cache(final InvocationContext ic) throws Throwable
-    {
-        final CDIJCacheHelper.MethodMeta methodMeta = helper.findMeta(ic);
-
-        final String cacheName = methodMeta.getCacheRemoveAllCacheName();
-
-        final CacheResolverFactory cacheResolverFactory = methodMeta.getCacheRemoveAllResolverFactory();
-        final CacheKeyInvocationContext<CacheRemoveAll> context = new CacheKeyInvocationContextImpl<CacheRemoveAll>(
-                ic, methodMeta.getCacheRemoveAll(), cacheName, methodMeta);
-        final CacheResolver cacheResolver = cacheResolverFactory.getCacheResolver(context);
-        final Cache<Object, Object> cache = cacheResolver.resolveCache(context);
-
-        final boolean afterInvocation = methodMeta.isCachePutAfter();
-        if (!afterInvocation)
-        {
-            cache.removeAll();
-        }
-
-        final Object result;
-        try
-        {
-            result = ic.proceed();
-        }
-        catch (final Throwable t)
-        {
-            if (afterInvocation)
-            {
-                if (helper.isIncluded(t.getClass(), methodMeta.getCacheRemoveAll().evictFor(), methodMeta.getCacheRemoveAll().noEvictFor()))
-                {
-                    cache.removeAll();
-                }
-            }
-            throw t;
-        }
-
-        if (afterInvocation)
-        {
-            cache.removeAll();
-        }
-
-        return result;
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CacheRemoveInterceptor.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CacheRemoveInterceptor.java
deleted file mode 100644
index 8835ac8..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CacheRemoveInterceptor.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.cdi;
-
-import java.io.Serializable;
-
-import javax.annotation.Priority;
-import javax.cache.Cache;
-import javax.cache.annotation.CacheKeyInvocationContext;
-import javax.cache.annotation.CacheRemove;
-import javax.cache.annotation.CacheResolver;
-import javax.cache.annotation.CacheResolverFactory;
-import javax.cache.annotation.GeneratedCacheKey;
-import javax.inject.Inject;
-import javax.interceptor.AroundInvoke;
-import javax.interceptor.Interceptor;
-import javax.interceptor.InvocationContext;
-
-@CacheRemove
-@Interceptor
-@Priority(/*LIBRARY_BEFORE*/1000)
-public class CacheRemoveInterceptor implements Serializable
-{
-    @Inject
-    private CDIJCacheHelper helper;
-
-    @AroundInvoke
-    public Object cache(final InvocationContext ic) throws Throwable
-    {
-        final CDIJCacheHelper.MethodMeta methodMeta = helper.findMeta(ic);
-
-        final String cacheName = methodMeta.getCacheRemoveCacheName();
-
-        final CacheResolverFactory cacheResolverFactory = methodMeta.getCacheRemoveResolverFactory();
-        final CacheKeyInvocationContext<CacheRemove> context = new CacheKeyInvocationContextImpl<CacheRemove>(
-                ic, methodMeta.getCacheRemove(), cacheName, methodMeta);
-        final CacheResolver cacheResolver = cacheResolverFactory.getCacheResolver(context);
-        final Cache<Object, Object> cache = cacheResolver.resolveCache(context);
-
-        final GeneratedCacheKey cacheKey = methodMeta.getCacheRemoveKeyGenerator().generateCacheKey(context);
-        final CacheRemove cacheRemove = methodMeta.getCacheRemove();
-        final boolean afterInvocation = methodMeta.isCacheRemoveAfter();
-
-        if (!afterInvocation)
-        {
-            cache.remove(cacheKey);
-        }
-
-        final Object result;
-        try
-        {
-            result = ic.proceed();
-        }
-        catch (final Throwable t)
-        {
-            if (afterInvocation)
-            {
-                if (helper.isIncluded(t.getClass(), cacheRemove.evictFor(), cacheRemove.noEvictFor()))
-                {
-                    cache.remove(cacheKey);
-                }
-            }
-
-            throw t;
-        }
-
-        if (afterInvocation)
-        {
-            cache.remove(cacheKey);
-        }
-
-        return result;
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CacheResolverFactoryImpl.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CacheResolverFactoryImpl.java
deleted file mode 100644
index 442fba1..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CacheResolverFactoryImpl.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.cdi;
-
-import javax.cache.Cache;
-import javax.cache.CacheManager;
-import javax.cache.Caching;
-import javax.cache.annotation.CacheMethodDetails;
-import javax.cache.annotation.CacheResolver;
-import javax.cache.annotation.CacheResolverFactory;
-import javax.cache.annotation.CacheResult;
-import javax.cache.configuration.MutableConfiguration;
-import javax.cache.spi.CachingProvider;
-import java.lang.annotation.Annotation;
-
-public class CacheResolverFactoryImpl implements CacheResolverFactory
-{
-    private final CacheManager cacheManager;
-    private final CachingProvider provider;
-
-    public CacheResolverFactoryImpl()
-    {
-        provider = Caching.getCachingProvider();
-        cacheManager = provider.getCacheManager(provider.getDefaultURI(), provider.getDefaultClassLoader());
-    }
-
-    @Override
-    public CacheResolver getCacheResolver(CacheMethodDetails<? extends Annotation> cacheMethodDetails)
-    {
-        return findCacheResolver(cacheMethodDetails.getCacheName());
-    }
-
-    @Override
-    public CacheResolver getExceptionCacheResolver(final CacheMethodDetails<CacheResult> cacheMethodDetails)
-    {
-        final String exceptionCacheName = cacheMethodDetails.getCacheAnnotation().exceptionCacheName();
-        if (exceptionCacheName == null || exceptionCacheName.isEmpty())
-        {
-            throw new IllegalArgumentException("CacheResult.exceptionCacheName() not specified");
-        }
-        return findCacheResolver(exceptionCacheName);
-    }
-
-    private CacheResolver findCacheResolver(String exceptionCacheName)
-    {
-        Cache<?, ?> cache = cacheManager.getCache(exceptionCacheName);
-        if (cache == null)
-        {
-            cache = createCache(exceptionCacheName);
-        }
-        return new CacheResolverImpl(cache);
-    }
-
-    private Cache<?, ?> createCache(final String exceptionCacheName)
-    {
-        cacheManager.createCache(exceptionCacheName, new MutableConfiguration<Object, Object>().setStoreByValue(false));
-        return cacheManager.getCache(exceptionCacheName);
-    }
-
-    public void release()
-    {
-        cacheManager.close();
-        provider.close();
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CacheResolverImpl.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CacheResolverImpl.java
deleted file mode 100644
index 17f983c..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CacheResolverImpl.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.cdi;
-
-import javax.cache.Cache;
-import javax.cache.annotation.CacheInvocationContext;
-import javax.cache.annotation.CacheResolver;
-import java.lang.annotation.Annotation;
-
-public class CacheResolverImpl implements CacheResolver
-{
-    private final Cache<?, ?> delegate;
-
-    public CacheResolverImpl(final Cache<?, ?> cache)
-    {
-        delegate = cache;
-    }
-
-    @Override
-    public <K, V> Cache<K, V> resolveCache(final CacheInvocationContext<? extends Annotation> cacheInvocationContext)
-    {
-        return (Cache<K, V>) delegate;
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CacheResultInterceptor.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CacheResultInterceptor.java
deleted file mode 100644
index e58ad34..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/CacheResultInterceptor.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.cdi;
-
-import java.io.Serializable;
-import java.lang.reflect.Method;
-import javax.annotation.Priority;
-import javax.cache.Cache;
-import javax.cache.annotation.CacheKeyInvocationContext;
-import javax.cache.annotation.CacheResolver;
-import javax.cache.annotation.CacheResolverFactory;
-import javax.cache.annotation.CacheResult;
-import javax.cache.annotation.GeneratedCacheKey;
-import javax.inject.Inject;
-import javax.interceptor.AroundInvoke;
-import javax.interceptor.Interceptor;
-import javax.interceptor.InvocationContext;
-
-@CacheResult
-@Interceptor
-@Priority(/*LIBRARY_BEFORE*/1000)
-public class CacheResultInterceptor implements Serializable
-{
-    @Inject
-    private CDIJCacheHelper helper;
-
-    @AroundInvoke
-    public Object cache(final InvocationContext ic) throws Throwable
-    {
-        final CDIJCacheHelper.MethodMeta methodMeta = helper.findMeta(ic);
-
-        final String cacheName = methodMeta.getCacheResultCacheName();
-
-        final CacheResult cacheResult = methodMeta.getCacheResult();
-        final CacheKeyInvocationContext<CacheResult> context = new CacheKeyInvocationContextImpl<CacheResult>(
-                ic, cacheResult, cacheName, methodMeta);
-
-        final CacheResolverFactory cacheResolverFactory = methodMeta.getCacheResultResolverFactory();
-        final CacheResolver cacheResolver = cacheResolverFactory.getCacheResolver(context);
-        final Cache<Object, Object> cache = cacheResolver.resolveCache(context);
-
-        final GeneratedCacheKey cacheKey = methodMeta.getCacheResultKeyGenerator().generateCacheKey(context);
-
-        Cache<Object, Object> exceptionCache = null; // lazily created
-
-        Object result;
-        if (!cacheResult.skipGet())
-        {
-            result = cache.get(cacheKey);
-            if (result != null)
-            {
-                return result;
-            }
-
-
-            if (!cacheResult.exceptionCacheName().isEmpty())
-            {
-                exceptionCache = cacheResolverFactory.getExceptionCacheResolver(context).resolveCache(context);
-                final Object exception = exceptionCache.get(cacheKey);
-                if (exception != null)
-                {
-                    throw Throwable.class.cast(exception);
-                }
-            }
-        }
-
-        try
-        {
-            result = ic.proceed();
-            if (result != null)
-            {
-                cache.put(cacheKey, result);
-            }
-
-            return result;
-        }
-        catch (final Throwable t)
-        {
-            if (helper.isIncluded(t.getClass(), cacheResult.cachedExceptions(), cacheResult.nonCachedExceptions()))
-            {
-                if (exceptionCache == null)
-                {
-                    exceptionCache = cacheResolverFactory.getExceptionCacheResolver(context).resolveCache(context);
-                }
-                exceptionCache.put(cacheKey, t);
-            }
-            throw t;
-        }
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/GeneratedCacheKeyImpl.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/GeneratedCacheKeyImpl.java
deleted file mode 100644
index 95c53c1..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/GeneratedCacheKeyImpl.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.cdi;
-
-import javax.cache.annotation.GeneratedCacheKey;
-import java.util.Arrays;
-
-public class GeneratedCacheKeyImpl implements GeneratedCacheKey
-{
-    private final Object[] params;
-    private final int hash;
-
-    public GeneratedCacheKeyImpl(final Object[] parameters)
-    {
-        this.params = parameters;
-        this.hash = Arrays.deepHashCode(parameters);
-    }
-
-    @Override
-    public boolean equals(final Object o)
-    {
-        if (this == o)
-        {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass())
-        {
-            return false;
-        }
-        final GeneratedCacheKeyImpl that = GeneratedCacheKeyImpl.class.cast(o);
-        return Arrays.deepEquals(params, that.params);
-
-    }
-
-    @Override
-    public int hashCode()
-    {
-        return hash;
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/MakeJCacheCDIInterceptorFriendly.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/MakeJCacheCDIInterceptorFriendly.java
deleted file mode 100644
index 72dc883..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/cdi/MakeJCacheCDIInterceptorFriendly.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.cdi;
-
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Type;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
-import javax.cache.annotation.CachePut;
-import javax.cache.annotation.CacheRemove;
-import javax.cache.annotation.CacheRemoveAll;
-import javax.cache.annotation.CacheResult;
-import javax.enterprise.context.ApplicationScoped;
-import javax.enterprise.context.spi.CreationalContext;
-import javax.enterprise.event.Observes;
-import javax.enterprise.inject.Any;
-import javax.enterprise.inject.Default;
-import javax.enterprise.inject.spi.AfterBeanDiscovery;
-import javax.enterprise.inject.spi.AnnotatedType;
-import javax.enterprise.inject.spi.Bean;
-import javax.enterprise.inject.spi.BeanManager;
-import javax.enterprise.inject.spi.BeforeBeanDiscovery;
-import javax.enterprise.inject.spi.Extension;
-import javax.enterprise.inject.spi.InjectionPoint;
-import javax.enterprise.inject.spi.InjectionTarget;
-import javax.enterprise.inject.spi.PassivationCapable;
-import javax.enterprise.inject.spi.ProcessAnnotatedType;
-import javax.enterprise.util.AnnotationLiteral;
-
-import static java.util.Arrays.asList;
-
-// TODO: observe annotated type (or maybe sthg else) to cache data and inject this extension (used as metadata cache)
-// to get class model and this way allow to add cache annotation on the fly - == avoid java pure reflection to get metadata
-public class MakeJCacheCDIInterceptorFriendly implements Extension
-{
-    private static final AtomicInteger id = new AtomicInteger();
-    private static final boolean USE_ID = !Boolean.getBoolean("org.apache.commons.jcs.cdi.skip-id");
-
-    private boolean needHelper = true;
-
-    protected void discoverInterceptorBindings(final @Observes BeforeBeanDiscovery beforeBeanDiscoveryEvent,
-                                               final BeanManager bm)
-    {
-        // CDI 1.1 will just pick createAnnotatedType(X) as beans so we'll skip our HelperBean
-        // but CDI 1.0 needs our HelperBean + interceptors in beans.xml like:
-        /*
-        <beans xmlns="http://java.sun.com/xml/ns/javaee"
-               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-               xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
-              http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
-          <interceptors>
-            <class>org.apache.commons.jcs.jcache.cdi.CacheResultInterceptor</class>
-            <class>org.apache.commons.jcs.jcache.cdi.CacheRemoveAllInterceptor</class>
-            <class>org.apache.commons.jcs.jcache.cdi.CacheRemoveInterceptor</class>
-            <class>org.apache.commons.jcs.jcache.cdi.CachePutInterceptor</class>
-          </interceptors>
-        </beans>
-         */
-        bm.createAnnotatedType(CDIJCacheHelper.class);
-        for (final Class<?> interceptor : asList(
-                CachePutInterceptor.class, CacheRemoveInterceptor.class,
-                CacheRemoveAllInterceptor.class, CacheResultInterceptor.class)) {
-            beforeBeanDiscoveryEvent.addAnnotatedType(bm.createAnnotatedType(interceptor));
-        }
-        for (final Class<? extends Annotation> interceptor : asList(
-                CachePut.class, CacheRemove.class,
-                CacheRemoveAll.class, CacheResult.class)) {
-            beforeBeanDiscoveryEvent.addInterceptorBinding(interceptor);
-        }
-    }
-
-    protected void addHelper(final @Observes AfterBeanDiscovery afterBeanDiscovery,
-                             final BeanManager bm)
-    {
-        if (!needHelper) {
-            return;
-        }
-        /* CDI >= 1.1 only. Actually we shouldn't go here with CDI 1.1 since we defined the annotated type for the helper
-        final AnnotatedType<CDIJCacheHelper> annotatedType = bm.createAnnotatedType(CDIJCacheHelper.class);
-        final BeanAttributes<CDIJCacheHelper> beanAttributes = bm.createBeanAttributes(annotatedType);
-        final InjectionTarget<CDIJCacheHelper> injectionTarget = bm.createInjectionTarget(annotatedType);
-        final Bean<CDIJCacheHelper> bean = bm.createBean(beanAttributes, CDIJCacheHelper.class, new InjectionTargetFactory<CDIJCacheHelper>() {
-            @Override
-            public InjectionTarget<CDIJCacheHelper> createInjectionTarget(Bean<CDIJCacheHelper> bean) {
-                return injectionTarget;
-            }
-        });
-        */
-        final AnnotatedType<CDIJCacheHelper> annotatedType = bm.createAnnotatedType(CDIJCacheHelper.class);
-        final InjectionTarget<CDIJCacheHelper> injectionTarget = bm.createInjectionTarget(annotatedType);
-        final HelperBean bean = new HelperBean(annotatedType, injectionTarget, findIdSuffix());
-        afterBeanDiscovery.addBean(bean);
-    }
-
-    protected void vetoScannedCDIJCacheHelperQualifiers(final @Observes ProcessAnnotatedType<CDIJCacheHelper> pat) {
-        if (!needHelper) { // already seen, shouldn't really happen,just a protection
-            pat.veto();
-        }
-        needHelper = false;
-    }
-
-    // TODO: make it better for ear+cluster case with CDI 1.0
-    private String findIdSuffix() {
-        // big disadvantage is all deployments of a cluster needs to be in the exact same order but it works with ears
-        if (USE_ID) {
-            return "lib" + id.incrementAndGet();
-        }
-        return "default";
-    }
-
-    public static class HelperBean implements Bean<CDIJCacheHelper>, PassivationCapable {
-        private final AnnotatedType<CDIJCacheHelper> at;
-        private final InjectionTarget<CDIJCacheHelper> it;
-        private final HashSet<Annotation> qualifiers;
-        private final String id;
-
-        public HelperBean(final AnnotatedType<CDIJCacheHelper> annotatedType,
-                          final InjectionTarget<CDIJCacheHelper> injectionTarget,
-                          final String id) {
-            this.at = annotatedType;
-            this.it = injectionTarget;
-            this.id =  "JCS#CDIHelper#" + id;
-
-            this.qualifiers = new HashSet<Annotation>();
-            this.qualifiers.add(new AnnotationLiteral<Default>() {});
-            this.qualifiers.add(new AnnotationLiteral<Any>() {});
-        }
-
-        @Override
-        public Set<InjectionPoint> getInjectionPoints() {
-            return it.getInjectionPoints();
-        }
-
-        @Override
-        public Class<?> getBeanClass() {
-            return at.getJavaClass();
-        }
-
-        @Override
-        public boolean isNullable() {
-            return false;
-        }
-
-        @Override
-        public Set<Type> getTypes() {
-            return at.getTypeClosure();
-        }
-
-        @Override
-        public Set<Annotation> getQualifiers() {
-            return qualifiers;
-        }
-
-        @Override
-        public Class<? extends Annotation> getScope() {
-            return ApplicationScoped.class;
-        }
-
-        @Override
-        public String getName() {
-            return null;
-        }
-
-        @Override
-        public Set<Class<? extends Annotation>> getStereotypes() {
-            return Collections.emptySet();
-        }
-
-        @Override
-        public boolean isAlternative() {
-            return false;
-        }
-
-        @Override
-        public CDIJCacheHelper create(final CreationalContext<CDIJCacheHelper> context) {
-            final CDIJCacheHelper produce = it.produce(context);
-            it.inject(produce, context);
-            it.postConstruct(produce);
-            return produce;
-        }
-
-        @Override
-        public void destroy(final CDIJCacheHelper instance, final CreationalContext<CDIJCacheHelper> context) {
-            it.preDestroy(instance);
-            it.dispose(instance);
-            context.release();
-        }
-
-        @Override
-        public String getId() {
-            return id;
-        }
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/jmx/ConfigurableMBeanServerIdBuilder.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/jmx/ConfigurableMBeanServerIdBuilder.java
deleted file mode 100644
index cd5746f..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/jmx/ConfigurableMBeanServerIdBuilder.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.jmx;
-
-import javax.management.ListenerNotFoundException;
-import javax.management.MBeanNotificationInfo;
-import javax.management.MBeanServer;
-import javax.management.MBeanServerBuilder;
-import javax.management.MBeanServerDelegate;
-import javax.management.Notification;
-import javax.management.NotificationFilter;
-import javax.management.NotificationListener;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-public class ConfigurableMBeanServerIdBuilder extends MBeanServerBuilder
-{
-    private static ConcurrentMap<Key, MBeanServer> JVM_SINGLETONS = new ConcurrentHashMap<Key, MBeanServer>();
-
-    private static class Key
-    {
-        private final String domain;
-        private final MBeanServer outer;
-
-        private Key(final String domain, final MBeanServer outer)
-        {
-            this.domain = domain;
-            this.outer = outer;
-        }
-
-        @Override
-        public boolean equals(final Object o)
-        {
-            if (this == o)
-                return true;
-            if (o == null || getClass() != o.getClass())
-                return false;
-
-            final Key key = Key.class.cast(o);
-            return !(domain != null ? !domain.equals(key.domain) : key.domain != null)
-                    && !(outer != null ? !outer.equals(key.outer) : key.outer != null);
-
-        }
-
-        @Override
-        public int hashCode()
-        {
-            int result = domain != null ? domain.hashCode() : 0;
-            result = 31 * result + (outer != null ? outer.hashCode() : 0);
-            return result;
-        }
-    }
-
-    @Override
-    public MBeanServer newMBeanServer(final String defaultDomain, final MBeanServer outer, final MBeanServerDelegate delegate)
-    {
-        final Key key = new Key(defaultDomain, outer);
-        MBeanServer server = JVM_SINGLETONS.get(key);
-        if (server == null)
-        {
-            server = super.newMBeanServer(defaultDomain, outer, new ForceIdMBeanServerDelegate(delegate));
-            final MBeanServer existing = JVM_SINGLETONS.putIfAbsent(key, server);
-            if (existing != null)
-            {
-                server = existing;
-            }
-        }
-        return server;
-    }
-
-    private class ForceIdMBeanServerDelegate extends MBeanServerDelegate
-    {
-        private final MBeanServerDelegate delegate;
-
-        public ForceIdMBeanServerDelegate(final MBeanServerDelegate delegate)
-        {
-            this.delegate = delegate;
-        }
-
-        @Override
-        public String getMBeanServerId()
-        {
-            return System.getProperty("org.jsr107.tck.management.agentId", delegate.getMBeanServerId());
-        }
-
-        @Override
-        public String getSpecificationName()
-        {
-            return delegate.getSpecificationName();
-        }
-
-        @Override
-        public String getSpecificationVersion()
-        {
-            return delegate.getSpecificationVersion();
-        }
-
-        @Override
-        public String getSpecificationVendor()
-        {
-            return delegate.getSpecificationVendor();
-        }
-
-        @Override
-        public String getImplementationName()
-        {
-            return delegate.getImplementationName();
-        }
-
-        @Override
-        public String getImplementationVersion()
-        {
-            return delegate.getImplementationVersion();
-        }
-
-        @Override
-        public String getImplementationVendor()
-        {
-            return delegate.getImplementationVendor();
-        }
-
-        @Override
-        public MBeanNotificationInfo[] getNotificationInfo()
-        {
-            return delegate.getNotificationInfo();
-        }
-
-        @Override
-        public void addNotificationListener(final NotificationListener listener, final NotificationFilter filter, final Object handback)
-                throws IllegalArgumentException
-        {
-            delegate.addNotificationListener(listener, filter, handback);
-        }
-
-        @Override
-        public void removeNotificationListener(final NotificationListener listener, final NotificationFilter filter, final Object handback)
-                throws ListenerNotFoundException
-        {
-            delegate.removeNotificationListener(listener, filter, handback);
-        }
-
-        @Override
-        public void removeNotificationListener(final NotificationListener listener) throws ListenerNotFoundException
-        {
-            delegate.removeNotificationListener(listener);
-        }
-
-        @Override
-        public void sendNotification(final Notification notification)
-        {
-            delegate.sendNotification(notification);
-        }
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/jmx/JCSCacheMXBean.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/jmx/JCSCacheMXBean.java
deleted file mode 100644
index 3e352df..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/jmx/JCSCacheMXBean.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.jmx;
-
-import javax.cache.Cache;
-import javax.cache.configuration.CompleteConfiguration;
-import javax.cache.configuration.Configuration;
-import javax.cache.management.CacheMXBean;
-
-public class JCSCacheMXBean<K, V> implements CacheMXBean
-{
-    private final Cache<K, V> delegate;
-
-    public JCSCacheMXBean(final Cache<K, V> delegate)
-    {
-        this.delegate = delegate;
-    }
-
-    private Configuration<K, V> config()
-    {
-        return delegate.getConfiguration(Configuration.class);
-    }
-
-    private CompleteConfiguration<K, V> completeConfig()
-    {
-        return delegate.getConfiguration(CompleteConfiguration.class);
-    }
-
-    @Override
-    public String getKeyType()
-    {
-        return config().getKeyType().getName();
-    }
-
-    @Override
-    public String getValueType()
-    {
-        return config().getValueType().getName();
-    }
-
-    @Override
-    public boolean isReadThrough()
-    {
-        try
-        {
-            return completeConfig().isReadThrough();
-        }
-        catch (final Exception e)
-        {
-            return false;
-        }
-    }
-
-    @Override
-    public boolean isWriteThrough()
-    {
-        try
-        {
-            return completeConfig().isWriteThrough();
-        }
-        catch (final Exception e)
-        {
-            return false;
-        }
-    }
-
-    @Override
-    public boolean isStoreByValue()
-    {
-        return config().isStoreByValue();
-    }
-
-    @Override
-    public boolean isStatisticsEnabled()
-    {
-        try
-        {
-            return completeConfig().isStatisticsEnabled();
-        }
-        catch (final Exception e)
-        {
-            return false;
-        }
-    }
-
-    @Override
-    public boolean isManagementEnabled()
-    {
-        try
-        {
-            return completeConfig().isManagementEnabled();
-        }
-        catch (final Exception e)
-        {
-            return false;
-        }
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/jmx/JCSCacheStatisticsMXBean.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/jmx/JCSCacheStatisticsMXBean.java
deleted file mode 100644
index df1273a..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/jmx/JCSCacheStatisticsMXBean.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.jmx;
-
-import org.apache.commons.jcs.jcache.Statistics;
-
-import javax.cache.management.CacheStatisticsMXBean;
-
-public class JCSCacheStatisticsMXBean implements CacheStatisticsMXBean
-{
-    private final Statistics statistics;
-
-    public JCSCacheStatisticsMXBean(final Statistics stats)
-    {
-        this.statistics = stats;
-    }
-
-    @Override
-    public void clear()
-    {
-        statistics.reset();
-    }
-
-    @Override
-    public long getCacheHits()
-    {
-        return statistics.getHits();
-    }
-
-    @Override
-    public float getCacheHitPercentage()
-    {
-        final long hits = getCacheHits();
-        if (hits == 0)
-        {
-            return 0;
-        }
-        return (float) hits / getCacheGets() * 100.0f;
-    }
-
-    @Override
-    public long getCacheMisses()
-    {
-        return statistics.getMisses();
-    }
-
-    @Override
-    public float getCacheMissPercentage()
-    {
-        final long misses = getCacheMisses();
-        if (misses == 0)
-        {
-            return 0;
-        }
-        return (float) misses / getCacheGets() * 100.0f;
-    }
-
-    @Override
-    public long getCacheGets()
-    {
-        return getCacheHits() + getCacheMisses();
-    }
-
-    @Override
-    public long getCachePuts()
-    {
-        return statistics.getPuts();
-    }
-
-    @Override
-    public long getCacheRemovals()
-    {
-        return statistics.getRemovals();
-    }
-
-    @Override
-    public long getCacheEvictions()
-    {
-        return statistics.getEvictions();
-    }
-
-    @Override
-    public float getAverageGetTime()
-    {
-        return averageTime(statistics.getTimeTakenForGets());
-    }
-
-    @Override
-    public float getAveragePutTime()
-    {
-        return averageTime(statistics.getTimeTakenForPuts());
-    }
-
-    @Override
-    public float getAverageRemoveTime()
-    {
-        return averageTime(statistics.getTimeTakenForRemovals());
-    }
-
-    private float averageTime(final long timeTaken)
-    {
-        final long gets = getCacheGets();
-        if (timeTaken == 0 || gets == 0)
-        {
-            return 0;
-        }
-        return timeTaken / gets;
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/jmx/JMXs.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/jmx/JMXs.java
deleted file mode 100644
index c8597df..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/jmx/JMXs.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.jmx;
-
-import javax.management.MBeanServer;
-import javax.management.MBeanServerFactory;
-import javax.management.ObjectName;
-import java.lang.management.ManagementFactory;
-
-public class JMXs
-{
-    private static final MBeanServer SERVER = findMBeanServer();
-
-    public static MBeanServer server()
-    {
-        return SERVER;
-    }
-
-    public static void register(final ObjectName on, final Object bean)
-    {
-        if (!SERVER.isRegistered(on))
-        {
-            try
-            {
-                SERVER.registerMBean(bean, on);
-            }
-            catch (final Exception e)
-            {
-                throw new IllegalStateException(e.getMessage(), e);
-            }
-        }
-    }
-
-    public static void unregister(final ObjectName on)
-    {
-        if (SERVER.isRegistered(on))
-        {
-            try
-            {
-                SERVER.unregisterMBean(on);
-            }
-            catch (final Exception e)
-            {
-                // no-op
-            }
-        }
-    }
-
-    private static MBeanServer findMBeanServer()
-    {
-        if (System.getProperty("javax.management.builder.initial") != null)
-        {
-            return MBeanServerFactory.createMBeanServer();
-        }
-        return ManagementFactory.getPlatformMBeanServer();
-    }
-
-    private JMXs()
-    {
-        // no-op
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/lang/DefaultSubsitutor.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/lang/DefaultSubsitutor.java
deleted file mode 100644
index f3a0ecb..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/lang/DefaultSubsitutor.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.commons.jcs.jcache.lang;
-
-public class DefaultSubsitutor implements Subsitutor
-{
-    @Override
-    public String substitute(final String value)
-    {
-        if (value.startsWith("${") && value.endsWith("}")) {
-            return System.getProperty(value.substring("${".length(), value.length() - 1), value);
-        }
-        return value;
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/lang/Lang3Substitutor.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/lang/Lang3Substitutor.java
deleted file mode 100644
index bd00eff..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/lang/Lang3Substitutor.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.commons.jcs.jcache.lang;
-
-import org.apache.commons.lang3.text.StrSubstitutor;
-
-import java.util.HashMap;
-import java.util.Map;
-
-public class Lang3Substitutor implements Subsitutor
-{
-    private static final StrSubstitutor SUBSTITUTOR = new StrSubstitutor(new HashMap<String, Object>() {{
-        putAll(Map.class.cast(System.getProperties()));
-        putAll(System.getenv());
-    }});
-
-    @Override
-    public String substitute(final String value)
-    {
-        return SUBSTITUTOR.replace(value);
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/lang/Subsitutor.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/lang/Subsitutor.java
deleted file mode 100644
index 9c049a2..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/lang/Subsitutor.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.commons.jcs.jcache.lang;
-
-public interface Subsitutor
-{
-    String substitute(String value);
-
-    public static class Helper {
-        public static final Subsitutor INSTANCE;
-        static {
-            Subsitutor value = null;
-            for (final String name : new String[]
-            { // ordered by features
-                    "org.apache.commons.jcs.jcache.lang.Lang3Substitutor",
-                    "org.apache.commons.jcs.jcache.lang.DefaultSubsitutor"
-            })
-            {
-                try
-                {
-                    value = Subsitutor.class.cast(
-                            Subsitutor.class.getClassLoader().loadClass(name).newInstance());
-                    value.substitute("${java.version}"); // ensure it works
-                }
-                catch (final Throwable e) // not Exception otherwise NoClassDefFoundError
-                {
-                    // no-op: next
-                }
-            }
-            if (value == null) {
-                throw new IllegalStateException("Can't find a " + Subsitutor.class.getName());
-            }
-            INSTANCE = value;
-        }
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/proxy/ClassLoaderAwareCache.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/proxy/ClassLoaderAwareCache.java
deleted file mode 100644
index 7a01488..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/proxy/ClassLoaderAwareCache.java
+++ /dev/null
@@ -1,493 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.proxy;
-
-import org.apache.commons.jcs.jcache.JCSCache;
-
-import javax.cache.Cache;
-import javax.cache.CacheManager;
-import javax.cache.configuration.CacheEntryListenerConfiguration;
-import javax.cache.configuration.Configuration;
-import javax.cache.integration.CompletionListener;
-import javax.cache.processor.EntryProcessor;
-import javax.cache.processor.EntryProcessorException;
-import javax.cache.processor.EntryProcessorResult;
-import java.io.Serializable;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-
-// don't use a proxy, reflection is too slow here :(
-public class ClassLoaderAwareCache<K, V> implements Cache<K, V>
-{
-    private final ClassLoader loader;
-    private final JCSCache<K, V> delegate;
-
-    public ClassLoaderAwareCache(final ClassLoader loader, final JCSCache<K, V> delegate)
-    {
-        this.loader = loader;
-        this.delegate = delegate;
-    }
-
-    private ClassLoader before(final Thread thread)
-    {
-        final ClassLoader tccl = thread.getContextClassLoader();
-        thread.setContextClassLoader(loader);
-        return tccl;
-    }
-
-    public V get(final K key)
-    {
-        final Thread thread = Thread.currentThread();
-        final ClassLoader loader = before(thread);
-        try
-        {
-            return delegate.get(key);
-        }
-        finally
-        {
-            thread.setContextClassLoader(loader);
-        }
-    }
-
-    public Map<K, V> getAll(final Set<? extends K> keys)
-    {
-        final Thread thread = Thread.currentThread();
-        final ClassLoader loader = before(thread);
-        try
-        {
-            return delegate.getAll(keys);
-        }
-        finally
-        {
-            thread.setContextClassLoader(loader);
-        }
-    }
-
-    public boolean containsKey(final K key)
-    {
-        final Thread thread = Thread.currentThread();
-        final ClassLoader loader = before(thread);
-        try
-        {
-            return delegate.containsKey(key);
-        }
-        finally
-        {
-            thread.setContextClassLoader(loader);
-        }
-    }
-
-    public void loadAll(final Set<? extends K> keys, boolean replaceExistingValues, final CompletionListener completionListener)
-    {
-        final Thread thread = Thread.currentThread();
-        final ClassLoader loader = before(thread);
-        try
-        {
-            delegate.loadAll(keys, replaceExistingValues, completionListener);
-        }
-        finally
-        {
-            thread.setContextClassLoader(loader);
-        }
-    }
-
-    public void put(final K key, final V value)
-    {
-        final Thread thread = Thread.currentThread();
-        final ClassLoader loader = before(thread);
-        try
-        {
-            delegate.put(key, value);
-        }
-        finally
-        {
-            thread.setContextClassLoader(loader);
-        }
-    }
-
-    public V getAndPut(final K key, final V value)
-    {
-        final Thread thread = Thread.currentThread();
-        final ClassLoader loader = before(thread);
-        try
-        {
-            return delegate.getAndPut(key, value);
-        }
-        finally
-        {
-            thread.setContextClassLoader(loader);
-        }
-    }
-
-    public void putAll(final Map<? extends K, ? extends V> map)
-    {
-        final Thread thread = Thread.currentThread();
-        final ClassLoader loader = before(thread);
-        try
-        {
-            delegate.putAll(map);
-        }
-        finally
-        {
-            thread.setContextClassLoader(loader);
-        }
-    }
-
-    public boolean putIfAbsent(final K key, final V value)
-    {
-        final Thread thread = Thread.currentThread();
-        final ClassLoader loader = before(thread);
-        try
-        {
-            return delegate.putIfAbsent(key, value);
-        }
-        finally
-        {
-            thread.setContextClassLoader(loader);
-        }
-    }
-
-    public boolean remove(final K key)
-    {
-        final Thread thread = Thread.currentThread();
-        final ClassLoader loader = before(thread);
-        try
-        {
-            return delegate.remove(key);
-        }
-        finally
-        {
-            thread.setContextClassLoader(loader);
-        }
-    }
-
-    public boolean remove(final K key, final V oldValue)
-    {
-        final Thread thread = Thread.currentThread();
-        final ClassLoader loader = before(thread);
-        try
-        {
-            return delegate.remove(key, oldValue);
-        }
-        finally
-        {
-            thread.setContextClassLoader(loader);
-        }
-    }
-
-    public V getAndRemove(final K key)
-    {
-        final Thread thread = Thread.currentThread();
-        final ClassLoader loader = before(thread);
-        try
-        {
-            return delegate.getAndRemove(key);
-        }
-        finally
-        {
-            thread.setContextClassLoader(loader);
-        }
-    }
-
-    public boolean replace(final K key, final V oldValue, final V newValue)
-    {
-        final Thread thread = Thread.currentThread();
-        final ClassLoader loader = before(thread);
-        try
-        {
-            return delegate.replace(key, oldValue, newValue);
-        }
-        finally
-        {
-            thread.setContextClassLoader(loader);
-        }
-    }
-
-    public boolean replace(final K key, final V value)
-    {
-        final Thread thread = Thread.currentThread();
-        final ClassLoader loader = before(thread);
-        try
-        {
-            return delegate.replace(key, value);
-        }
-        finally
-        {
-            thread.setContextClassLoader(loader);
-        }
-    }
-
-    public V getAndReplace(final K key, final V value)
-    {
-        final Thread thread = Thread.currentThread();
-        final ClassLoader loader = before(thread);
-        try
-        {
-            return delegate.getAndReplace(key, value);
-        }
-        finally
-        {
-            thread.setContextClassLoader(loader);
-        }
-    }
-
-    public void removeAll(final Set<? extends K> keys)
-    {
-        final Thread thread = Thread.currentThread();
-        final ClassLoader loader = before(thread);
-        try
-        {
-            delegate.removeAll(keys);
-        }
-        finally
-        {
-            thread.setContextClassLoader(loader);
-        }
-    }
-
-    @Override
-    public void removeAll()
-    {
-        final Thread thread = Thread.currentThread();
-        final ClassLoader loader = before(thread);
-        try
-        {
-            delegate.removeAll();
-        }
-        finally
-        {
-            thread.setContextClassLoader(loader);
-        }
-    }
-
-    @Override
-    public void clear()
-    {
-        final Thread thread = Thread.currentThread();
-        final ClassLoader loader = before(thread);
-        try
-        {
-            delegate.clear();
-        }
-        finally
-        {
-            thread.setContextClassLoader(loader);
-        }
-    }
-
-    public <C extends Configuration<K, V>> C getConfiguration(final Class<C> clazz)
-    {
-        final Thread thread = Thread.currentThread();
-        final ClassLoader loader = before(thread);
-        try
-        {
-            return delegate.getConfiguration(clazz);
-        }
-        finally
-        {
-            thread.setContextClassLoader(loader);
-        }
-    }
-
-    public <T> T invoke(final K key, final EntryProcessor<K, V, T> entryProcessor, final Object... arguments) throws EntryProcessorException
-    {
-        final Thread thread = Thread.currentThread();
-        final ClassLoader loader = before(thread);
-        try
-        {
-            return delegate.invoke(key, entryProcessor, arguments);
-        }
-        finally
-        {
-            thread.setContextClassLoader(loader);
-        }
-    }
-
-    public <T> Map<K, EntryProcessorResult<T>> invokeAll(Set<? extends K> keys, EntryProcessor<K, V, T> entryProcessor, Object... arguments)
-    {
-        final Thread thread = Thread.currentThread();
-        final ClassLoader loader = before(thread);
-        try
-        {
-            return delegate.invokeAll(keys, entryProcessor, arguments);
-        }
-        finally
-        {
-            thread.setContextClassLoader(loader);
-        }
-    }
-
-    @Override
-    public String getName()
-    {
-        final Thread thread = Thread.currentThread();
-        final ClassLoader loader = before(thread);
-        try
-        {
-            return delegate.getName();
-        }
-        finally
-        {
-            thread.setContextClassLoader(loader);
-        }
-    }
-
-    @Override
-    public CacheManager getCacheManager()
-    {
-        final Thread thread = Thread.currentThread();
-        final ClassLoader loader = before(thread);
-        try
-        {
-            return delegate.getCacheManager();
-        }
-        finally
-        {
-            thread.setContextClassLoader(loader);
-        }
-    }
-
-    @Override
-    public void close()
-    {
-        final Thread thread = Thread.currentThread();
-        final ClassLoader loader = before(thread);
-        try
-        {
-            delegate.close();
-        }
-        finally
-        {
-            thread.setContextClassLoader(loader);
-        }
-    }
-
-    @Override
-    public boolean isClosed()
-    {
-        final Thread thread = Thread.currentThread();
-        final ClassLoader loader = before(thread);
-        try
-        {
-            return delegate.isClosed();
-        }
-        finally
-        {
-            thread.setContextClassLoader(loader);
-        }
-    }
-
-    @Override
-    public <T> T unwrap(final Class<T> clazz)
-    {
-        final Thread thread = Thread.currentThread();
-        final ClassLoader loader = before(thread);
-        try
-        {
-            return delegate.unwrap(clazz);
-        }
-        finally
-        {
-            thread.setContextClassLoader(loader);
-        }
-    }
-
-    public void registerCacheEntryListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration)
-    {
-        final Thread thread = Thread.currentThread();
-        final ClassLoader loader = before(thread);
-        try
-        {
-            delegate.registerCacheEntryListener(cacheEntryListenerConfiguration);
-        }
-        finally
-        {
-            thread.setContextClassLoader(loader);
-        }
-    }
-
-    public void deregisterCacheEntryListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration)
-    {
-        final Thread thread = Thread.currentThread();
-        final ClassLoader loader = before(thread);
-        try
-        {
-            delegate.deregisterCacheEntryListener(cacheEntryListenerConfiguration);
-        }
-        finally
-        {
-            thread.setContextClassLoader(loader);
-        }
-    }
-
-    @Override
-    public Iterator<Entry<K, V>> iterator()
-    {
-        final Thread thread = Thread.currentThread();
-        final ClassLoader loader = before(thread);
-        try
-        {
-            return delegate.iterator();
-        }
-        finally
-        {
-            thread.setContextClassLoader(loader);
-        }
-    }
-
-    @Override
-    public boolean equals(final Object obj)
-    {
-        if (ClassLoaderAwareCache.class.isInstance(obj))
-        {
-            return delegate.equals(ClassLoaderAwareCache.class.cast(obj).delegate);
-        }
-        return super.equals(obj);
-    }
-
-    @Override
-    public int hashCode()
-    {
-        return delegate.hashCode();
-    }
-
-    public static <K extends Serializable, V extends Serializable> Cache<K, V> wrap(final ClassLoader loader, final JCSCache<K, V> delegate)
-    {
-        ClassLoader dontWrapLoader = ClassLoaderAwareCache.class.getClassLoader();
-        while (dontWrapLoader != null)
-        {
-            if (loader == dontWrapLoader)
-            {
-                return delegate;
-            }
-            dontWrapLoader = dontWrapLoader.getParent();
-        }
-        return new ClassLoaderAwareCache<K, V>(loader, delegate);
-    }
-
-    public static <K extends Serializable, V extends Serializable> JCSCache<K, V> getDelegate(final Cache<?, ?> cache)
-    {
-        if (JCSCache.class.isInstance(cache))
-        {
-            return (JCSCache<K, V>) cache;
-        }
-        return ((ClassLoaderAwareCache<K, V>) cache).delegate;
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/proxy/ExceptionWrapperHandler.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/proxy/ExceptionWrapperHandler.java
deleted file mode 100644
index 9c81427..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/proxy/ExceptionWrapperHandler.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.proxy;
-
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-
-public class ExceptionWrapperHandler<T> implements InvocationHandler
-{
-    private final T delegate;
-    private final Constructor<? extends RuntimeException> wrapper;
-
-    public ExceptionWrapperHandler(final T delegate, final Class<? extends RuntimeException> exceptionType)
-    {
-        this.delegate = delegate;
-        try
-        {
-            this.wrapper = exceptionType.getConstructor(Throwable.class);
-        }
-        catch (final NoSuchMethodException e)
-        {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    @Override
-    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable
-    {
-        try
-        {
-            return method.invoke(delegate, args);
-        }
-        catch (final InvocationTargetException ite)
-        {
-            final Throwable e = ite.getCause();
-            if (RuntimeException.class.isInstance(e))
-            {
-                final RuntimeException re;
-                try
-                {
-                    re = wrapper.newInstance(e);
-                }
-                catch (final Exception e1)
-                {
-                    throw new IllegalArgumentException(e1);
-                }
-                throw re;
-            }
-            throw e;
-        }
-    }
-
-    public static <T> T newProxy(final ClassLoader loader, final T delegate, final Class<? extends RuntimeException> exceptionType,
-            final Class<T> apis)
-    {
-        return (T) Proxy.newProxyInstance(loader, new Class<?>[] { apis }, new ExceptionWrapperHandler<T>(delegate, exceptionType));
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/serialization/Serializations.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/serialization/Serializations.java
deleted file mode 100644
index b024719..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/serialization/Serializations.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.serialization;
-
-import org.apache.commons.jcs.engine.behavior.IElementSerializer;
-
-public class Serializations
-{
-    public static <K> K copy(final IElementSerializer serializer, final ClassLoader loader, final K key)
-    {
-        try
-        {
-            return serializer.deSerialize(serializer.serialize(key), loader);
-        }
-        catch ( final Exception e)
-        {
-            throw new IllegalStateException(e);
-        }
-    }
-}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/thread/DaemonThreadFactory.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/thread/DaemonThreadFactory.java
deleted file mode 100644
index ce97d9d..0000000
--- a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs/jcache/thread/DaemonThreadFactory.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache.thread;
-
-
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.atomic.AtomicInteger;
-
-public class DaemonThreadFactory implements ThreadFactory
-{
-    private final AtomicInteger index = new AtomicInteger(1);
-    private final String prefix;
-
-    public DaemonThreadFactory(final String prefix)
-    {
-        this.prefix = prefix;
-    }
-
-    @Override
-    public Thread newThread( final Runnable runner )
-    {
-        final Thread t = new Thread( runner );
-        t.setName(prefix + index.getAndIncrement());
-        t.setDaemon(true);
-        return t;
-    }
-}
\ No newline at end of file
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/Asserts.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/Asserts.java
new file mode 100644
index 0000000..93c7fbd
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/Asserts.java
@@ -0,0 +1,36 @@
+package org.apache.commons.jcs3.jcache;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+public class Asserts
+{
+    public static void assertNotNull(final Object value, final String name)
+    {
+        if (value == null)
+        {
+            throw new NullPointerException(name + " is null");
+        }
+    }
+
+    private Asserts()
+    {
+        // no-op
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/EvictionListener.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/EvictionListener.java
new file mode 100644
index 0000000..111b04c
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/EvictionListener.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache;
+
+import org.apache.commons.jcs3.engine.control.event.behavior.IElementEvent;
+import org.apache.commons.jcs3.engine.control.event.behavior.IElementEventHandler;
+
+public class EvictionListener implements IElementEventHandler
+{
+    private final Statistics stats;
+
+    public EvictionListener(final Statistics statistics)
+    {
+        this.stats = statistics;
+    }
+
+    @Override
+    public void handleElementEvent(final IElementEvent event)
+    {
+        switch (event.getElementEvent())
+        {
+            case EXCEEDED_MAXLIFE_BACKGROUND:
+            case EXCEEDED_MAXLIFE_ONREQUEST:
+            case EXCEEDED_IDLETIME_ONREQUEST:
+            case EXCEEDED_IDLETIME_BACKGROUND:
+                stats.increaseEvictions(1);
+                break;
+        }
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/ExpiryAwareCache.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/ExpiryAwareCache.java
new file mode 100644
index 0000000..1b865c9
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/ExpiryAwareCache.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache;
+
+import java.util.Arrays;
+import java.util.Map;
+
+import javax.cache.Cache;
+import javax.cache.configuration.CacheEntryListenerConfiguration;
+import javax.cache.event.CacheEntryEvent;
+import javax.cache.event.EventType;
+
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes;
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+
+// allows us to plug some lifecycle callbacks on the core cache without impacting too much the core
+public class ExpiryAwareCache<A, B> extends CompositeCache<A, B>
+{
+    private Map<CacheEntryListenerConfiguration<A, B>, JCSListener<A, B>> listeners;
+    private Cache<A, B> cacheRef;
+
+    ExpiryAwareCache(final ICompositeCacheAttributes cattr, final IElementAttributes attr)
+    {
+        super(cattr, attr);
+    }
+
+    @Override
+    protected void doExpires(final ICacheElement<A, B> element)
+    {
+        super.doExpires(element);
+        for (final JCSListener<A, B> listener : listeners.values())
+        {
+            listener.onExpired(Arrays.<CacheEntryEvent<? extends A, ? extends B>> asList(new JCSCacheEntryEvent<A, B>(
+                    cacheRef, EventType.REMOVED, null, element.getKey(), element.getVal())));
+        }
+    }
+
+    void init(final Cache<A, B> cache, final Map<CacheEntryListenerConfiguration<A, B>, JCSListener<A, B>> listeners)
+    {
+        this.cacheRef = cache;
+        this.listeners = listeners;
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/ImmutableIterable.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/ImmutableIterable.java
new file mode 100644
index 0000000..ac54ee7
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/ImmutableIterable.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+
+public class ImmutableIterable<T> implements Iterable<T>
+{
+    private final Collection<T> delegate;
+
+    public ImmutableIterable(final Collection<T> delegate)
+    {
+        this.delegate = new ArrayList<>(delegate);
+    }
+
+    @Override
+    public Iterator<T> iterator()
+    {
+        return new ImmutableIterator<>(delegate.iterator());
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/ImmutableIterator.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/ImmutableIterator.java
new file mode 100644
index 0000000..535eac7
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/ImmutableIterator.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache;
+
+import java.util.Iterator;
+
+public class ImmutableIterator<T> implements Iterator<T>
+{
+    private final Iterator<T> delegate;
+
+    public ImmutableIterator(final Iterator<T> delegate)
+    {
+        this.delegate = delegate;
+    }
+
+    @Override
+    public boolean hasNext()
+    {
+        return delegate.hasNext();
+    }
+
+    @Override
+    public T next()
+    {
+        return delegate.next();
+    }
+
+    @Override
+    public void remove()
+    {
+        throw new UnsupportedOperationException("this iterator is immutable");
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/JCSCache.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/JCSCache.java
new file mode 100644
index 0000000..61b39a1
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/JCSCache.java
@@ -0,0 +1,994 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache;
+
+import static org.apache.commons.jcs3.jcache.Asserts.assertNotNull;
+import static org.apache.commons.jcs3.jcache.serialization.Serializations.copy;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import javax.cache.Cache;
+import javax.cache.CacheException;
+import javax.cache.CacheManager;
+import javax.cache.configuration.CacheEntryListenerConfiguration;
+import javax.cache.configuration.Configuration;
+import javax.cache.configuration.Factory;
+import javax.cache.event.CacheEntryEvent;
+import javax.cache.event.EventType;
+import javax.cache.expiry.Duration;
+import javax.cache.expiry.EternalExpiryPolicy;
+import javax.cache.expiry.ExpiryPolicy;
+import javax.cache.integration.CacheLoader;
+import javax.cache.integration.CacheLoaderException;
+import javax.cache.integration.CacheWriter;
+import javax.cache.integration.CacheWriterException;
+import javax.cache.integration.CompletionListener;
+import javax.cache.processor.EntryProcessor;
+import javax.cache.processor.EntryProcessorException;
+import javax.cache.processor.EntryProcessorResult;
+import javax.management.ObjectName;
+
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.ElementAttributes;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+import org.apache.commons.jcs3.jcache.jmx.JCSCacheMXBean;
+import org.apache.commons.jcs3.jcache.jmx.JCSCacheStatisticsMXBean;
+import org.apache.commons.jcs3.jcache.jmx.JMXs;
+import org.apache.commons.jcs3.jcache.proxy.ExceptionWrapperHandler;
+import org.apache.commons.jcs3.jcache.thread.DaemonThreadFactory;
+import org.apache.commons.jcs3.utils.serialization.StandardSerializer;
+
+// TODO: configure serializer
+public class JCSCache<K, V> implements Cache<K, V>
+{
+    private final ExpiryAwareCache<K, V> delegate;
+    private final JCSCachingManager manager;
+    private final JCSConfiguration<K, V> config;
+    private final CacheLoader<K, V> loader;
+    private final CacheWriter<? super K, ? super V> writer;
+    private final ExpiryPolicy expiryPolicy;
+    private final ObjectName cacheConfigObjectName;
+    private final ObjectName cacheStatsObjectName;
+    private final String name;
+    private volatile boolean closed = false;
+    private final Map<CacheEntryListenerConfiguration<K, V>, JCSListener<K, V>> listeners = new ConcurrentHashMap<>();
+    private final Statistics statistics = new Statistics();
+    private final ExecutorService pool;
+    private final IElementSerializer serializer; // using json/xml should work as well -> don't force Serializable
+
+
+    public JCSCache(final ClassLoader classLoader, final JCSCachingManager mgr,
+                    final String cacheName, final JCSConfiguration<K, V> configuration,
+                    final Properties properties, final ExpiryAwareCache<K, V> cache)
+    {
+        manager = mgr;
+
+        name = cacheName;
+
+        delegate = cache;
+        if (delegate.getElementAttributes() == null)
+        {
+            delegate.setElementAttributes(new ElementAttributes());
+        }
+        delegate.getElementAttributes().addElementEventHandler(new EvictionListener(statistics));
+
+        config = configuration;
+
+        final int poolSize = Integer.parseInt(property(properties, cacheName, "pool.size", "3"));
+        final DaemonThreadFactory threadFactory = new DaemonThreadFactory("JCS-JCache-" + cacheName + "-");
+        pool = poolSize > 0 ? Executors.newFixedThreadPool(poolSize, threadFactory) : Executors.newCachedThreadPool(threadFactory);
+
+        try
+        {
+            serializer = IElementSerializer.class.cast(classLoader.loadClass(property(properties, "serializer", cacheName, StandardSerializer.class.getName())).newInstance());
+        }
+        catch (final Exception e)
+        {
+            throw new IllegalArgumentException(e);
+        }
+
+        final Factory<CacheLoader<K, V>> cacheLoaderFactory = configuration.getCacheLoaderFactory();
+        if (cacheLoaderFactory == null)
+        {
+            loader = NoLoader.INSTANCE;
+        }
+        else
+        {
+            loader = ExceptionWrapperHandler
+                    .newProxy(classLoader, cacheLoaderFactory.create(), CacheLoaderException.class, CacheLoader.class);
+        }
+
+        final Factory<CacheWriter<? super K, ? super V>> cacheWriterFactory = configuration.getCacheWriterFactory();
+        if (cacheWriterFactory == null)
+        {
+            writer = NoWriter.INSTANCE;
+        }
+        else
+        {
+            writer = ExceptionWrapperHandler
+                    .newProxy(classLoader, cacheWriterFactory.create(), CacheWriterException.class, CacheWriter.class);
+        }
+
+        final Factory<ExpiryPolicy> expiryPolicyFactory = configuration.getExpiryPolicyFactory();
+        if (expiryPolicyFactory == null)
+        {
+            expiryPolicy = new EternalExpiryPolicy();
+        }
+        else
+        {
+            expiryPolicy = expiryPolicyFactory.create();
+        }
+
+        for (final CacheEntryListenerConfiguration<K, V> listener : config.getCacheEntryListenerConfigurations())
+        {
+            listeners.put(listener, new JCSListener<>(listener));
+        }
+        delegate.init(this, listeners);
+
+        statistics.setActive(config.isStatisticsEnabled());
+
+        final String mgrStr = manager.getURI().toString().replaceAll(",|:|=|\n", ".");
+        final String cacheStr = name.replaceAll(",|:|=|\n", ".");
+        try
+        {
+            cacheConfigObjectName = new ObjectName("javax.cache:type=CacheConfiguration,"
+                    + "CacheManager=" + mgrStr + "," + "Cache=" + cacheStr);
+            cacheStatsObjectName = new ObjectName("javax.cache:type=CacheStatistics,"
+                    + "CacheManager=" + mgrStr + "," + "Cache=" + cacheStr);
+        }
+        catch (final Exception e)
+        {
+            throw new IllegalArgumentException(e);
+        }
+        if (config.isManagementEnabled())
+        {
+            JMXs.register(cacheConfigObjectName, new JCSCacheMXBean<>(this));
+        }
+        if (config.isStatisticsEnabled())
+        {
+            JMXs.register(cacheStatsObjectName, new JCSCacheStatisticsMXBean(statistics));
+        }
+    }
+
+    private static String property(final Properties properties, final String cacheName, final String name, final String defaultValue)
+    {
+        return properties.getProperty(cacheName + "." + name, properties.getProperty(name, defaultValue));
+    }
+
+    private void assertNotClosed()
+    {
+        if (isClosed())
+        {
+            throw new IllegalStateException("cache closed");
+        }
+    }
+
+    @Override
+    public V get(final K key)
+    {
+        assertNotClosed();
+        assertNotNull(key, "key");
+        final long getStart = Times.now(false);
+        return doGetControllingExpiry(getStart, key, true, false, false, true);
+    }
+
+    private V doLoad(final K key, final boolean update, final long now, final boolean propagateLoadException)
+    {
+        V v = null;
+        try
+        {
+            v = loader.load(key);
+        }
+        catch (final CacheLoaderException e)
+        {
+            if (propagateLoadException)
+            {
+                throw e;
+            }
+        }
+        if (v != null)
+        {
+            final Duration duration = update ? expiryPolicy.getExpiryForUpdate() : expiryPolicy.getExpiryForCreation();
+            if (isNotZero(duration))
+            {
+                final IElementAttributes clone = delegate.getElementAttributes().clone();
+                if (ElementAttributes.class.isInstance(clone))
+                {
+                    ElementAttributes.class.cast(clone).setCreateTime();
+                }
+                final ICacheElement<K, V> element = updateElement(key, v, duration, clone);
+                try
+                {
+                    delegate.update(element);
+                }
+                catch (final IOException e)
+                {
+                    throw new CacheException(e);
+                }
+            }
+        }
+        return v;
+    }
+
+    private ICacheElement<K, V> updateElement(final K key, final V v, final Duration duration, final IElementAttributes attrs)
+    {
+        final ICacheElement<K, V> element = new CacheElement<K, V>(name, key, v);
+        if (duration != null)
+        {
+            attrs.setTimeFactorForMilliseconds(1);
+            final boolean eternal = duration.isEternal();
+            attrs.setIsEternal(eternal);
+            if (!eternal)
+            {
+                attrs.setLastAccessTimeNow();
+            }
+            // MaxLife = -1 to use IdleTime excepted if jcache.ccf asked for something else
+        }
+        element.setElementAttributes(attrs);
+        return element;
+    }
+
+    private void touch(final K key, final ICacheElement<K, V> element)
+    {
+        if (config.isStoreByValue())
+        {
+            final K copy = copy(serializer, manager.getClassLoader(), key);
+            try
+            {
+                delegate.update(new CacheElement<K, V>(name, copy, element.getVal(), element.getElementAttributes()));
+            }
+            catch (final IOException e)
+            {
+                throw new CacheException(e);
+            }
+        }
+    }
+
+    @Override
+    public Map<K, V> getAll(final Set<? extends K> keys)
+    {
+        assertNotClosed();
+        for (final K k : keys)
+        {
+            assertNotNull(k, "key");
+        }
+
+        final long now = Times.now(false);
+        final Map<K, V> result = new HashMap<>();
+        for (final K key : keys) {
+            assertNotNull(key, "key");
+
+            final ICacheElement<K, V> elt = delegate.get(key);
+            V val = elt != null ? elt.getVal() : null;
+            if (val == null && config.isReadThrough())
+            {
+                val = doLoad(key, false, now, false);
+                if (val != null)
+                {
+                    result.put(key, val);
+                }
+            }
+            else if (elt != null)
+            {
+                final Duration expiryForAccess = expiryPolicy.getExpiryForAccess();
+                if (isNotZero(expiryForAccess))
+                {
+                    touch(key, elt);
+                    result.put(key, val);
+                }
+                else
+                {
+                    forceExpires(key);
+                }
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public boolean containsKey(final K key)
+    {
+        assertNotClosed();
+        assertNotNull(key, "key");
+        return delegate.get(key) != null;
+    }
+
+    @Override
+    public void put(final K key, final V rawValue)
+    {
+        assertNotClosed();
+        assertNotNull(key, "key");
+        assertNotNull(rawValue, "value");
+
+        final ICacheElement<K, V> oldElt = delegate.get(key);
+        final V old = oldElt != null ? oldElt.getVal() : null;
+
+        final boolean storeByValue = config.isStoreByValue();
+        final V value = storeByValue ? copy(serializer, manager.getClassLoader(), rawValue) : rawValue;
+
+        final boolean created = old == null;
+        final Duration duration = created ? expiryPolicy.getExpiryForCreation() : expiryPolicy.getExpiryForUpdate();
+        if (isNotZero(duration))
+        {
+            final boolean statisticsEnabled = config.isStatisticsEnabled();
+            final long start = Times.now(false);
+
+            final K jcsKey = storeByValue ? copy(serializer, manager.getClassLoader(), key) : key;
+            final ICacheElement<K, V> element = updateElement( // reuse it to create basic structure
+                    jcsKey, value, created ? null : duration,
+                    oldElt != null ? oldElt.getElementAttributes() : delegate.getElementAttributes().clone());
+            if (created && duration != null) { // set maxLife
+                final IElementAttributes copy = element.getElementAttributes();
+                copy.setTimeFactorForMilliseconds(1);
+                final boolean eternal = duration.isEternal();
+                copy.setIsEternal(eternal);
+                if (ElementAttributes.class.isInstance(copy)) {
+                    ElementAttributes.class.cast(copy).setCreateTime();
+                }
+                if (!eternal)
+                {
+                    copy.setIsEternal(false);
+                    if (duration == expiryPolicy.getExpiryForAccess())
+                    {
+                        element.getElementAttributes().setIdleTime(duration.getTimeUnit().toMillis(duration.getDurationAmount()));
+                    }
+                    else
+                        {
+                        element.getElementAttributes().setMaxLife(duration.getTimeUnit().toMillis(duration.getDurationAmount()));
+                    }
+                }
+                element.setElementAttributes(copy);
+            }
+            writer.write(new JCSEntry<>(jcsKey, value));
+            try
+            {
+                delegate.update(element);
+            }
+            catch (final IOException e)
+            {
+                throw new CacheException(e);
+            }
+            for (final JCSListener<K, V> listener : listeners.values())
+            {
+                if (created)
+                {
+                    listener.onCreated(Arrays.<CacheEntryEvent<? extends K, ? extends V>> asList(new JCSCacheEntryEvent<>(this,
+                            EventType.CREATED, null, key, value)));
+                }
+                else
+                {
+                    listener.onUpdated(Arrays.<CacheEntryEvent<? extends K, ? extends V>> asList(new JCSCacheEntryEvent<>(this,
+                            EventType.UPDATED, old, key, value)));
+                }
+            }
+
+            if (statisticsEnabled)
+            {
+                statistics.increasePuts(1);
+                statistics.addPutTime(System.currentTimeMillis() - start);
+            }
+        }
+        else
+        {
+            if (!created)
+            {
+                forceExpires(key);
+            }
+        }
+    }
+
+    private static boolean isNotZero(final Duration duration)
+    {
+        return duration == null || !duration.isZero();
+    }
+
+    private void forceExpires(final K cacheKey)
+    {
+        final ICacheElement<K, V> elt = delegate.get(cacheKey);
+        delegate.remove(cacheKey);
+        for (final JCSListener<K, V> listener : listeners.values())
+        {
+            listener.onExpired(Arrays.<CacheEntryEvent<? extends K, ? extends V>> asList(new JCSCacheEntryEvent<K, V>(this,
+                    EventType.REMOVED, null, cacheKey, elt.getVal())));
+        }
+    }
+
+    @Override
+    public V getAndPut(final K key, final V value)
+    {
+        assertNotClosed();
+        assertNotNull(key, "key");
+        assertNotNull(value, "value");
+        final long getStart = Times.now(false);
+        final V v = doGetControllingExpiry(getStart, key, false, false, true, false);
+        put(key, value);
+        return v;
+    }
+
+    @Override
+    public void putAll(final Map<? extends K, ? extends V> map)
+    {
+        assertNotClosed();
+        final TempStateCacheView<K, V> view = new TempStateCacheView<>(this);
+        for (final Map.Entry<? extends K, ? extends V> e : map.entrySet())
+        {
+            view.put(e.getKey(), e.getValue());
+        }
+        view.merge();
+    }
+
+    @Override
+    public boolean putIfAbsent(final K key, final V value)
+    {
+        if (!containsKey(key))
+        {
+            put(key, value);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean remove(final K key)
+    {
+        assertNotClosed();
+        assertNotNull(key, "key");
+
+        final boolean statisticsEnabled = config.isStatisticsEnabled();
+        final long start = Times.now(!statisticsEnabled);
+
+        writer.delete(key);
+        final K cacheKey = key;
+
+        final ICacheElement<K, V> v = delegate.get(cacheKey);
+        delegate.remove(cacheKey);
+
+        final V value = v != null && v.getVal() != null ? v.getVal() : null;
+        boolean remove = v != null;
+        for (final JCSListener<K, V> listener : listeners.values())
+        {
+            listener.onRemoved(Arrays.<CacheEntryEvent<? extends K, ? extends V>> asList(new JCSCacheEntryEvent<>(this,
+                    EventType.REMOVED, null, key, value)));
+        }
+        if (remove && statisticsEnabled)
+        {
+            statistics.increaseRemovals(1);
+            statistics.addRemoveTime(Times.now(false) - start);
+        }
+        return remove;
+    }
+
+    @Override
+    public boolean remove(final K key, final V oldValue)
+    {
+        assertNotClosed();
+        assertNotNull(key, "key");
+        assertNotNull(oldValue, "oldValue");
+        final long getStart = Times.now(false);
+        final V v = doGetControllingExpiry(getStart, key, false, false, false, false);
+        if (oldValue.equals(v))
+        {
+            remove(key);
+            return true;
+        }
+        else if (v != null)
+        {
+            // weird but just for stats to be right (org.jsr107.tck.expiry.CacheExpiryTest.removeSpecifiedEntryShouldNotCallExpiryPolicyMethods())
+            expiryPolicy.getExpiryForAccess();
+        }
+        return false;
+    }
+
+    @Override
+    public V getAndRemove(final K key)
+    {
+        assertNotClosed();
+        assertNotNull(key, "key");
+        final long getStart = Times.now(false);
+        final V v = doGetControllingExpiry(getStart, key, false, false, true, false);
+        remove(key);
+        return v;
+    }
+
+    private V doGetControllingExpiry(final long getStart, final K key, final boolean updateAcess, final boolean forceDoLoad, final boolean skipLoad,
+            final boolean propagateLoadException)
+    {
+        final boolean statisticsEnabled = config.isStatisticsEnabled();
+        final ICacheElement<K, V> elt = delegate.get(key);
+        V v = elt != null ? elt.getVal() : null;
+        if (v == null && (config.isReadThrough() || forceDoLoad))
+        {
+            if (!skipLoad)
+            {
+                v = doLoad(key, false, getStart, propagateLoadException);
+            }
+        }
+        else if (statisticsEnabled)
+        {
+            if (v != null)
+            {
+                statistics.increaseHits(1);
+            }
+            else
+            {
+                statistics.increaseMisses(1);
+            }
+        }
+
+        if (updateAcess && elt != null)
+        {
+            final Duration expiryForAccess = expiryPolicy.getExpiryForAccess();
+            if (!isNotZero(expiryForAccess))
+            {
+                forceExpires(key);
+            }
+            else if (expiryForAccess != null && (!elt.getElementAttributes().getIsEternal() || !expiryForAccess.isEternal()))
+            {
+                try
+                {
+                    delegate.update(updateElement(key, elt.getVal(), expiryForAccess, elt.getElementAttributes()));
+                }
+                catch (final IOException e)
+                {
+                    throw new CacheException(e);
+                }
+            }
+        }
+        if (statisticsEnabled && v != null)
+        {
+            statistics.addGetTime(Times.now(false) - getStart);
+        }
+        return v;
+    }
+
+    @Override
+    public boolean replace(final K key, final V oldValue, final V newValue)
+    {
+        assertNotClosed();
+        assertNotNull(key, "key");
+        assertNotNull(oldValue, "oldValue");
+        assertNotNull(newValue, "newValue");
+        final boolean statisticsEnabled = config.isStatisticsEnabled();
+        final ICacheElement<K, V> elt = delegate.get(key);
+        if (elt != null)
+        {
+            V value = elt.getVal();
+            if (value != null && statisticsEnabled)
+            {
+                statistics.increaseHits(1);
+            }
+            if (value == null && config.isReadThrough())
+            {
+                value = doLoad(key, false, Times.now(false), false);
+            }
+            if (value != null && value.equals(oldValue))
+            {
+                put(key, newValue);
+                return true;
+            }
+            else if (value != null)
+            {
+                final Duration expiryForAccess = expiryPolicy.getExpiryForAccess();
+                if (expiryForAccess != null && (!elt.getElementAttributes().getIsEternal() || !expiryForAccess.isEternal()))
+                {
+                    try
+                    {
+                        delegate.update(updateElement(key, elt.getVal(), expiryForAccess, elt.getElementAttributes()));
+                    }
+                    catch (final IOException e)
+                    {
+                        throw new CacheException(e);
+                    }
+                }
+            }
+        }
+        else if (statisticsEnabled)
+        {
+            statistics.increaseMisses(1);
+        }
+        return false;
+    }
+
+    @Override
+    public boolean replace(final K key, final V value)
+    {
+        assertNotClosed();
+        assertNotNull(key, "key");
+        assertNotNull(value, "value");
+        boolean statisticsEnabled = config.isStatisticsEnabled();
+        if (containsKey(key))
+        {
+            if (statisticsEnabled)
+            {
+                statistics.increaseHits(1);
+            }
+            put(key, value);
+            return true;
+        }
+        else if (statisticsEnabled)
+        {
+            statistics.increaseMisses(1);
+        }
+        return false;
+    }
+
+    @Override
+    public V getAndReplace(final K key, final V value)
+    {
+        assertNotClosed();
+        assertNotNull(key, "key");
+        assertNotNull(value, "value");
+
+        final boolean statisticsEnabled = config.isStatisticsEnabled();
+
+        final ICacheElement<K, V> elt = delegate.get(key);
+        if (elt != null)
+        {
+            V oldValue = elt.getVal();
+            if (oldValue == null && config.isReadThrough())
+            {
+                oldValue = doLoad(key, false, Times.now(false), false);
+            }
+            else if (statisticsEnabled)
+            {
+                statistics.increaseHits(1);
+            }
+            put(key, value);
+            return oldValue;
+        }
+        else if (statisticsEnabled)
+        {
+            statistics.increaseMisses(1);
+        }
+        return null;
+    }
+
+    @Override
+    public void removeAll(final Set<? extends K> keys)
+    {
+        assertNotClosed();
+        assertNotNull(keys, "keys");
+        for (final K k : keys)
+        {
+            remove(k);
+        }
+    }
+
+    @Override
+    public void removeAll()
+    {
+        assertNotClosed();
+        for (final K k : delegate.getKeySet())
+        {
+            remove(k);
+        }
+    }
+
+    @Override
+    public void clear()
+    {
+        assertNotClosed();
+        try
+        {
+            delegate.removeAll();
+        }
+        catch (final IOException e)
+        {
+            throw new CacheException(e);
+        }
+    }
+
+    @Override
+    public <C2 extends Configuration<K, V>> C2 getConfiguration(final Class<C2> clazz)
+    {
+        assertNotClosed();
+        return clazz.cast(config);
+    }
+
+    @Override
+    public void loadAll(final Set<? extends K> keys, final boolean replaceExistingValues, final CompletionListener completionListener)
+    {
+        assertNotClosed();
+        assertNotNull(keys, "keys");
+        for (final K k : keys)
+        {
+            assertNotNull(k, "a key");
+        }
+        pool.submit(() -> doLoadAll(keys, replaceExistingValues, completionListener));
+    }
+
+    private void doLoadAll(final Set<? extends K> keys, final boolean replaceExistingValues, final CompletionListener completionListener)
+    {
+        try
+        {
+            final long now = Times.now(false);
+            for (final K k : keys)
+            {
+                if (replaceExistingValues)
+                {
+                    doLoad(k, containsKey(k), now, completionListener != null);
+                    continue;
+                }
+                else if (containsKey(k))
+                {
+                    continue;
+                }
+                doGetControllingExpiry(now, k, true, true, false, completionListener != null);
+            }
+        }
+        catch (final RuntimeException e)
+        {
+            if (completionListener != null)
+            {
+                completionListener.onException(e);
+                return;
+            }
+        }
+        if (completionListener != null)
+        {
+            completionListener.onCompletion();
+        }
+    }
+
+    @Override
+    public <T> T invoke(final K key, final EntryProcessor<K, V, T> entryProcessor, final Object... arguments) throws EntryProcessorException
+    {
+        final TempStateCacheView<K, V> view = new TempStateCacheView<>(this);
+        final T t = doInvoke(view, key, entryProcessor, arguments);
+        view.merge();
+        return t;
+    }
+
+    private <T> T doInvoke(final TempStateCacheView<K, V> view, final K key, final EntryProcessor<K, V, T> entryProcessor,
+            final Object... arguments)
+    {
+        assertNotClosed();
+        assertNotNull(entryProcessor, "entryProcessor");
+        assertNotNull(key, "key");
+        try
+        {
+            if (config.isStatisticsEnabled())
+            {
+                if (containsKey(key))
+                {
+                    statistics.increaseHits(1);
+                }
+                else
+                {
+                    statistics.increaseMisses(1);
+                }
+            }
+            return entryProcessor.process(new JCSMutableEntry<>(view, key), arguments);
+        }
+        catch (final Exception ex)
+        {
+            return throwEntryProcessorException(ex);
+        }
+    }
+
+    private static <T> T throwEntryProcessorException(final Exception ex)
+    {
+        if (EntryProcessorException.class.isInstance(ex))
+        {
+            throw EntryProcessorException.class.cast(ex);
+        }
+        throw new EntryProcessorException(ex);
+    }
+
+    @Override
+    public <T> Map<K, EntryProcessorResult<T>> invokeAll(final Set<? extends K> keys, final EntryProcessor<K, V, T> entryProcessor,
+            final Object... arguments)
+    {
+        assertNotClosed();
+        assertNotNull(entryProcessor, "entryProcessor");
+        final Map<K, EntryProcessorResult<T>> results = new HashMap<>();
+        for (final K k : keys)
+        {
+            try
+            {
+                final T invoke = invoke(k, entryProcessor, arguments);
+                if (invoke != null)
+                {
+                    results.put(k, () -> invoke);
+                }
+            }
+            catch (final Exception e)
+            {
+                results.put(k, () -> throwEntryProcessorException(e));
+            }
+        }
+        return results;
+    }
+
+    @Override
+    public void registerCacheEntryListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration)
+    {
+        assertNotClosed();
+        if (listeners.containsKey(cacheEntryListenerConfiguration))
+        {
+            throw new IllegalArgumentException(cacheEntryListenerConfiguration + " already registered");
+        }
+        listeners.put(cacheEntryListenerConfiguration, new JCSListener<>(cacheEntryListenerConfiguration));
+        config.addListener(cacheEntryListenerConfiguration);
+    }
+
+    @Override
+    public void deregisterCacheEntryListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration)
+    {
+        assertNotClosed();
+        listeners.remove(cacheEntryListenerConfiguration);
+        config.removeListener(cacheEntryListenerConfiguration);
+    }
+
+    @Override
+    public Iterator<Entry<K, V>> iterator()
+    {
+        assertNotClosed();
+        final Iterator<K> keys = new HashSet<K>(delegate.getKeySet()).iterator();
+        return new Iterator<Entry<K, V>>()
+        {
+            private K lastKey = null;
+
+            @Override
+            public boolean hasNext()
+            {
+                return keys.hasNext();
+            }
+
+            @Override
+            public Entry<K, V> next()
+            {
+                lastKey = keys.next();
+                return new JCSEntry<>(lastKey, get(lastKey));
+            }
+
+            @Override
+            public void remove()
+            {
+                if (isClosed() || lastKey == null)
+                {
+                    throw new IllegalStateException(isClosed() ? "cache closed" : "call next() before remove()");
+                }
+                JCSCache.this.remove(lastKey);
+            }
+        };
+    }
+
+    @Override
+    public String getName()
+    {
+        assertNotClosed();
+        return name;
+    }
+
+    @Override
+    public CacheManager getCacheManager()
+    {
+        assertNotClosed();
+        return manager;
+    }
+
+    @Override
+    public synchronized void close()
+    {
+        if (isClosed())
+        {
+            return;
+        }
+
+        for (final Runnable task : pool.shutdownNow()) {
+            task.run();
+        }
+
+        manager.release(getName());
+        closed = true;
+        close(loader);
+        close(writer);
+        close(expiryPolicy);
+        for (final JCSListener<K, V> listener : listeners.values())
+        {
+            close(listener);
+        }
+        listeners.clear();
+        JMXs.unregister(cacheConfigObjectName);
+        JMXs.unregister(cacheStatsObjectName);
+        try
+        {
+            delegate.removeAll();
+        }
+        catch (final IOException e)
+        {
+            throw new CacheException(e);
+        }
+    }
+
+    private static void close(final Object potentiallyCloseable)
+    {
+        if (Closeable.class.isInstance(potentiallyCloseable))
+        {
+            Closeable.class.cast(potentiallyCloseable);
+        }
+    }
+
+    @Override
+    public boolean isClosed()
+    {
+        return closed;
+    }
+
+    @Override
+    public <T> T unwrap(final Class<T> clazz)
+    {
+        assertNotClosed();
+        if (clazz.isInstance(this))
+        {
+            return clazz.cast(this);
+        }
+        if (clazz.isAssignableFrom(Map.class) || clazz.isAssignableFrom(ConcurrentMap.class))
+        {
+            return clazz.cast(delegate);
+        }
+        throw new IllegalArgumentException(clazz.getName() + " not supported in unwrap");
+    }
+
+    public Statistics getStatistics()
+    {
+        return statistics;
+    }
+
+    public void enableManagement()
+    {
+        config.managementEnabled();
+        JMXs.register(cacheConfigObjectName, new JCSCacheMXBean<>(this));
+    }
+
+    public void disableManagement()
+    {
+        config.managementDisabled();
+        JMXs.unregister(cacheConfigObjectName);
+    }
+
+    public void enableStatistics()
+    {
+        config.statisticsEnabled();
+        statistics.setActive(true);
+        JMXs.register(cacheStatsObjectName, new JCSCacheStatisticsMXBean(statistics));
+    }
+
+    public void disableStatistics()
+    {
+        config.statisticsDisabled();
+        statistics.setActive(false);
+        JMXs.unregister(cacheStatsObjectName);
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/JCSCacheEntryEvent.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/JCSCacheEntryEvent.java
new file mode 100644
index 0000000..13ea943
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/JCSCacheEntryEvent.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache;
+
+import javax.cache.Cache;
+import javax.cache.event.CacheEntryEvent;
+import javax.cache.event.EventType;
+
+public class JCSCacheEntryEvent<K, V> extends CacheEntryEvent<K, V>
+{
+    /** Serial version */
+    private static final long serialVersionUID = 4761272981003897488L;
+
+    private final V old;
+    private final K key;
+    private final V value;
+
+    public JCSCacheEntryEvent(final Cache<K, V> source, final EventType eventType, final V old, final K key, final V value)
+    {
+        super(source, eventType);
+        this.old = old;
+        this.key = key;
+        this.value = value;
+    }
+
+    @Override
+    public V getOldValue()
+    {
+        return old;
+    }
+
+    @Override
+    public boolean isOldValueAvailable()
+    {
+        return old != null;
+    }
+
+    @Override
+    public K getKey()
+    {
+        return key;
+    }
+
+    @Override
+    public V getValue()
+    {
+        return value;
+    }
+
+    @Override
+    public <T> T unwrap(final Class<T> clazz)
+    {
+        if (clazz.isInstance(this))
+        {
+            return clazz.cast(this);
+        }
+        throw new IllegalArgumentException(clazz.getName() + " not supported in unwrap");
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/JCSCachingManager.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/JCSCachingManager.java
new file mode 100644
index 0000000..8cb5a98
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/JCSCachingManager.java
@@ -0,0 +1,400 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache;
+
+import static org.apache.commons.jcs3.jcache.Asserts.assertNotNull;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.cache.Cache;
+import javax.cache.CacheManager;
+import javax.cache.configuration.Configuration;
+import javax.cache.spi.CachingProvider;
+
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes;
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+import org.apache.commons.jcs3.engine.control.CompositeCache;
+import org.apache.commons.jcs3.engine.control.CompositeCacheConfigurator;
+import org.apache.commons.jcs3.engine.control.CompositeCacheManager;
+import org.apache.commons.jcs3.jcache.lang.Subsitutor;
+import org.apache.commons.jcs3.jcache.proxy.ClassLoaderAwareCache;
+
+public class JCSCachingManager implements CacheManager
+{
+    private static final Subsitutor SUBSTITUTOR = Subsitutor.Helper.INSTANCE;
+    private static final String DEFAULT_CONFIG =
+        "jcs.default=DC\n" +
+        "jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes\n" +
+        "jcs.default.cacheattributes.MaxObjects=200001\n" +
+        "jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache\n" +
+        "jcs.default.cacheattributes.UseMemoryShrinker=true\n" +
+        "jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600\n" +
+        "jcs.default.cacheattributes.ShrinkerIntervalSeconds=60\n" +
+        "jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes\n" +
+        "jcs.default.elementattributes.IsEternal=false\n" +
+        "jcs.default.elementattributes.MaxLife=700\n" +
+        "jcs.default.elementattributes.IdleTime=1800\n" +
+        "jcs.default.elementattributes.IsSpool=true\n" +
+        "jcs.default.elementattributes.IsRemote=true\n" +
+        "jcs.default.elementattributes.IsLateral=true\n";
+
+    private static class InternalManager extends CompositeCacheManager
+    {
+        protected static InternalManager create()
+        {
+            return new InternalManager();
+        }
+
+        @Override
+        protected CompositeCacheConfigurator newConfigurator()
+        {
+            return new CompositeCacheConfigurator()
+            {
+                @Override
+                protected <K, V> CompositeCache<K, V> newCache(
+                        final ICompositeCacheAttributes cca, final IElementAttributes ea)
+                {
+                    return new ExpiryAwareCache<K, V>( cca, ea );
+                }
+            };
+        }
+
+        @Override // needed to call it from JCSCachingManager
+        protected void initialize() {
+            super.initialize();
+        }
+    }
+
+    private final CachingProvider provider;
+    private final URI uri;
+    private final ClassLoader loader;
+    private final Properties properties;
+    private final ConcurrentMap<String, Cache<?, ?>> caches = new ConcurrentHashMap<>();
+    private final Properties configProperties;
+    private volatile boolean closed = false;
+    private final InternalManager delegate = InternalManager.create();
+
+    public JCSCachingManager(final CachingProvider provider, final URI uri, final ClassLoader loader, final Properties properties)
+    {
+        this.provider = provider;
+        this.uri = uri;
+        this.loader = loader;
+        this.properties = readConfig(uri, loader, properties);
+        this.configProperties = properties;
+
+        delegate.setJmxName(CompositeCacheManager.JMX_OBJECT_NAME
+                + ",provider=" + provider.hashCode()
+                + ",uri=" + uri.toString().replaceAll(",|:|=|\n", ".")
+                + ",classloader=" + loader.hashCode()
+                + ",properties=" + this.properties.hashCode());
+        delegate.initialize();
+        delegate.configure(this.properties);
+    }
+
+    private Properties readConfig(final URI uri, final ClassLoader loader, final Properties properties) {
+        final Properties props = new Properties();
+        try {
+            if (JCSCachingProvider.DEFAULT_URI.toString().equals(uri.toString()) || uri.toURL().getProtocol().equals("jcs"))
+            {
+
+                final Enumeration<URL> resources = loader.getResources(uri.getPath());
+                if (!resources.hasMoreElements()) // default
+                {
+                    props.load(new ByteArrayInputStream(DEFAULT_CONFIG.getBytes("UTF-8")));
+                }
+                else
+                {
+                    do
+                    {
+                        addProperties(resources.nextElement(), props);
+                    }
+                    while (resources.hasMoreElements());
+                }
+            }
+            else
+            {
+                props.load(uri.toURL().openStream());
+            }
+        } catch (final IOException e) {
+            throw new IllegalStateException(e);
+        }
+
+        if (properties != null)
+        {
+            props.putAll(properties);
+        }
+
+        for (final Map.Entry<Object, Object> entry : props.entrySet()) {
+            if (entry.getValue() == null)
+            {
+                continue;
+            }
+            final String substitute = SUBSTITUTOR.substitute(entry.getValue().toString());
+            if (!substitute.equals(entry.getValue()))
+            {
+                entry.setValue(substitute);
+            }
+        }
+        return props;
+    }
+
+    private void addProperties(final URL url, final Properties aggregator)
+    {
+        InputStream inStream = null;
+        try
+        {
+            inStream = url.openStream();
+            aggregator.load(inStream);
+        }
+        catch (final IOException e)
+        {
+            throw new IllegalArgumentException(e);
+        }
+        finally
+        {
+            if (inStream != null)
+            {
+                try
+                {
+                    inStream.close();
+                }
+                catch (final IOException e)
+                {
+                    // no-op
+                }
+            }
+        }
+    }
+
+    private void assertNotClosed()
+    {
+        if (isClosed())
+        {
+            throw new IllegalStateException("cache manager closed");
+        }
+    }
+
+    @Override
+    // TODO: use configuration + handle not serializable key/values
+    public <K, V, C extends Configuration<K, V>> Cache<K, V> createCache(final String cacheName, final C configuration)
+            throws IllegalArgumentException
+    {
+        assertNotClosed();
+        assertNotNull(cacheName, "cacheName");
+        assertNotNull(configuration, "configuration");
+        final Class<?> keyType = configuration == null ? Object.class : configuration.getKeyType();
+        final Class<?> valueType = configuration == null ? Object.class : configuration.getValueType();
+        if (!caches.containsKey(cacheName))
+        {
+            final Cache<K, V> cache = ClassLoaderAwareCache.wrap(loader,
+                    new JCSCache/*<K, V>*/(
+                            loader, this, cacheName,
+                            new JCSConfiguration/*<K, V>*/(configuration, keyType, valueType),
+                            properties,
+                            ExpiryAwareCache.class.cast(delegate.getCache(cacheName))));
+            caches.putIfAbsent(cacheName, cache);
+        }
+        else
+        {
+            throw new javax.cache.CacheException("cache " + cacheName + " already exists");
+        }
+        return (Cache<K, V>) getCache(cacheName, keyType, valueType);
+    }
+
+    @Override
+    public void destroyCache(final String cacheName)
+    {
+        assertNotClosed();
+        assertNotNull(cacheName, "cacheName");
+        final Cache<?, ?> cache = caches.remove(cacheName);
+        if (cache != null && !cache.isClosed())
+        {
+            cache.clear();
+            cache.close();
+        }
+    }
+
+    @Override
+    public void enableManagement(final String cacheName, final boolean enabled)
+    {
+        assertNotClosed();
+        assertNotNull(cacheName, "cacheName");
+        final JCSCache<?, ?> cache = getJCSCache(cacheName);
+        if (cache != null)
+        {
+            if (enabled)
+            {
+                cache.enableManagement();
+            }
+            else
+            {
+                cache.disableManagement();
+            }
+        }
+    }
+
+    private JCSCache<?, ?> getJCSCache(final String cacheName)
+    {
+        final Cache<?, ?> cache = caches.get(cacheName);
+        return JCSCache.class.cast(ClassLoaderAwareCache.getDelegate(cache));
+    }
+
+    @Override
+    public void enableStatistics(final String cacheName, final boolean enabled)
+    {
+        assertNotClosed();
+        assertNotNull(cacheName, "cacheName");
+        final JCSCache<?, ?> cache = getJCSCache(cacheName);
+        if (cache != null)
+        {
+            if (enabled)
+            {
+                cache.enableStatistics();
+            }
+            else
+            {
+                cache.disableStatistics();
+            }
+        }
+    }
+
+    @Override
+    public synchronized void close()
+    {
+        if (isClosed())
+        {
+            return;
+        }
+
+        assertNotClosed();
+        for (final Cache<?, ?> c : caches.values())
+        {
+            c.close();
+        }
+        caches.clear();
+        closed = true;
+        if (JCSCachingProvider.class.isInstance(provider))
+        {
+            JCSCachingProvider.class.cast(provider).remove(this);
+        }
+        delegate.shutDown();
+    }
+
+    @Override
+    public <T> T unwrap(final Class<T> clazz)
+    {
+        if (clazz.isInstance(this))
+        {
+            return clazz.cast(this);
+        }
+        throw new IllegalArgumentException(clazz.getName() + " not supported in unwrap");
+    }
+
+    @Override
+    public boolean isClosed()
+    {
+        return closed;
+    }
+
+    @Override
+    public <K, V> Cache<K, V> getCache(final String cacheName)
+    {
+        assertNotClosed();
+        assertNotNull(cacheName, "cacheName");
+        return (Cache<K, V>) doGetCache(cacheName, Object.class, Object.class);
+    }
+
+    @Override
+    public Iterable<String> getCacheNames()
+    {
+        return new ImmutableIterable<>(caches.keySet());
+    }
+
+    @Override
+    public <K, V> Cache<K, V> getCache(final String cacheName, final Class<K> keyType, final Class<V> valueType)
+    {
+        assertNotClosed();
+        assertNotNull(cacheName, "cacheName");
+        assertNotNull(keyType, "keyType");
+        assertNotNull(valueType, "valueType");
+        try
+        {
+            return doGetCache(cacheName, keyType, valueType);
+        }
+        catch (final IllegalArgumentException iae)
+        {
+            throw new ClassCastException(iae.getMessage());
+        }
+    }
+
+    private <K, V> Cache<K, V> doGetCache(final String cacheName, final Class<K> keyType, final Class<V> valueType)
+    {
+        final Cache<K, V> cache = (Cache<K, V>) caches.get(cacheName);
+        if (cache == null)
+        {
+            return null;
+        }
+
+        final Configuration<K, V> config = cache.getConfiguration(Configuration.class);
+        if ((keyType != null && !config.getKeyType().isAssignableFrom(keyType))
+                || (valueType != null && !config.getValueType().isAssignableFrom(valueType)))
+        {
+            throw new IllegalArgumentException("this cache is <" + config.getKeyType().getName() + ", " + config.getValueType().getName()
+                    + "> " + " and not <" + keyType.getName() + ", " + valueType.getName() + ">");
+        }
+        return cache;
+    }
+
+    @Override
+    public CachingProvider getCachingProvider()
+    {
+        return provider;
+    }
+
+    @Override
+    public URI getURI()
+    {
+        return uri;
+    }
+
+    @Override
+    public ClassLoader getClassLoader()
+    {
+        return loader;
+    }
+
+    @Override
+    public Properties getProperties()
+    {
+        return configProperties;
+    }
+
+    public void release(final String name) {
+        caches.remove(name);
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/JCSCachingProvider.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/JCSCachingProvider.java
new file mode 100644
index 0000000..52929c3
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/JCSCachingProvider.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache;
+
+import javax.cache.CacheManager;
+import javax.cache.configuration.OptionalFeature;
+import javax.cache.spi.CachingProvider;
+import java.net.URI;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+public class JCSCachingProvider implements CachingProvider
+{
+    public static final URI DEFAULT_URI = URI.create("jcs://jcache.ccf");
+
+    private final ConcurrentMap<ClassLoader, ConcurrentMap<URI, CacheManager>> cacheManagersByLoader = new ConcurrentHashMap<>();
+
+    @Override
+    public CacheManager getCacheManager(final URI inUri, final ClassLoader inClassLoader, final Properties properties)
+    {
+        final URI uri = inUri != null ? inUri : getDefaultURI();
+        final ClassLoader classLoader = inClassLoader != null ? inClassLoader : getDefaultClassLoader();
+
+        ConcurrentMap<URI, CacheManager> managers = cacheManagersByLoader.get(classLoader);
+        if (managers == null)
+        {
+            managers = new ConcurrentHashMap<>();
+            final ConcurrentMap<URI, CacheManager> existingManagers = cacheManagersByLoader.putIfAbsent(classLoader, managers);
+            if (existingManagers != null)
+            {
+                managers = existingManagers;
+            }
+        }
+
+        CacheManager mgr = managers.get(uri);
+        if (mgr == null)
+        {
+            mgr = new JCSCachingManager(this, uri, classLoader, properties);
+            final CacheManager existing = managers.putIfAbsent(uri, mgr);
+            if (existing != null)
+            {
+                mgr = existing;
+            }
+        }
+
+        return mgr;
+    }
+
+    @Override
+    public URI getDefaultURI()
+    {
+        return DEFAULT_URI;
+    }
+
+    @Override
+    public void close()
+    {
+        for (final Map<URI, CacheManager> v : cacheManagersByLoader.values())
+        {
+            for (final CacheManager m : v.values())
+            {
+                m.close();
+            }
+            v.clear();
+        }
+        cacheManagersByLoader.clear();
+    }
+
+    @Override
+    public void close(final ClassLoader classLoader)
+    {
+        final Map<URI, CacheManager> cacheManagers = cacheManagersByLoader.remove(classLoader);
+        if (cacheManagers != null)
+        {
+            for (final CacheManager mgr : cacheManagers.values())
+            {
+                mgr.close();
+            }
+            cacheManagers.clear();
+        }
+    }
+
+    @Override
+    public void close(final URI uri, final ClassLoader classLoader)
+    {
+        final Map<URI, CacheManager> cacheManagers = cacheManagersByLoader.remove(classLoader);
+        if (cacheManagers != null)
+        {
+            final CacheManager mgr = cacheManagers.remove(uri);
+            if (mgr != null)
+            {
+                mgr.close();
+            }
+        }
+    }
+
+    @Override
+    public CacheManager getCacheManager(final URI uri, final ClassLoader classLoader)
+    {
+        return getCacheManager(uri, classLoader, getDefaultProperties());
+    }
+
+    @Override
+    public CacheManager getCacheManager()
+    {
+        return getCacheManager(getDefaultURI(), getDefaultClassLoader());
+    }
+
+    @Override
+    public boolean isSupported(final OptionalFeature optionalFeature)
+    {
+        return optionalFeature == OptionalFeature.STORE_BY_REFERENCE;
+    }
+
+    @Override
+    public ClassLoader getDefaultClassLoader()
+    {
+        return JCSCachingProvider.class.getClassLoader();
+    }
+
+    @Override
+    public Properties getDefaultProperties()
+    {
+        return new Properties();
+    }
+
+    void remove(final CacheManager mgr)
+    {
+        final ClassLoader classLoader = mgr.getClassLoader();
+        final Map<URI, CacheManager> mgrs = cacheManagersByLoader.get(classLoader);
+        if (mgrs != null)
+        {
+            mgrs.remove(mgr.getURI());
+            if (mgrs.isEmpty())
+            {
+                cacheManagersByLoader.remove(classLoader);
+            }
+        }
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/JCSConfiguration.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/JCSConfiguration.java
new file mode 100644
index 0000000..ffd7195
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/JCSConfiguration.java
@@ -0,0 +1,200 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ *  Copyright 2003-2010 Terracotta, Inc.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.commons.jcs3.jcache;
+
+import javax.cache.configuration.CacheEntryListenerConfiguration;
+import javax.cache.configuration.CompleteConfiguration;
+import javax.cache.configuration.Configuration;
+import javax.cache.configuration.Factory;
+import javax.cache.expiry.EternalExpiryPolicy;
+import javax.cache.expiry.ExpiryPolicy;
+import javax.cache.integration.CacheLoader;
+import javax.cache.integration.CacheWriter;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+public class JCSConfiguration<K, V> implements CompleteConfiguration<K, V>
+{
+
+    private final Class<K> keyType;
+    private final Class<V> valueType;
+    private final boolean storeByValue;
+    private final boolean readThrough;
+    private final boolean writeThrough;
+    private final Factory<CacheLoader<K, V>> cacheLoaderFactory;
+    private final Factory<CacheWriter<? super K, ? super V>> cacheWristerFactory;
+    private final Factory<ExpiryPolicy> expiryPolicyFactory;
+    private final Set<CacheEntryListenerConfiguration<K, V>> cacheEntryListenerConfigurations;
+
+    private volatile boolean statisticsEnabled;
+    private volatile boolean managementEnabled;
+
+    public JCSConfiguration(final Configuration<K, V> configuration, final Class<K> keyType, final Class<V> valueType)
+    {
+        this.keyType = keyType;
+        this.valueType = valueType;
+        if (configuration instanceof CompleteConfiguration)
+        {
+            final CompleteConfiguration<K, V> cConfiguration = (CompleteConfiguration<K, V>) configuration;
+            storeByValue = configuration.isStoreByValue();
+            readThrough = cConfiguration.isReadThrough();
+            writeThrough = cConfiguration.isWriteThrough();
+            statisticsEnabled = cConfiguration.isStatisticsEnabled();
+            managementEnabled = cConfiguration.isManagementEnabled();
+            cacheLoaderFactory = cConfiguration.getCacheLoaderFactory();
+            cacheWristerFactory = cConfiguration.getCacheWriterFactory();
+            this.expiryPolicyFactory = cConfiguration.getExpiryPolicyFactory();
+            cacheEntryListenerConfigurations = new HashSet<>();
+
+            final Iterable<CacheEntryListenerConfiguration<K, V>> entryListenerConfigurations = cConfiguration
+                    .getCacheEntryListenerConfigurations();
+            if (entryListenerConfigurations != null)
+            {
+                for (final CacheEntryListenerConfiguration<K, V> kvCacheEntryListenerConfiguration : entryListenerConfigurations)
+                {
+                    cacheEntryListenerConfigurations.add(kvCacheEntryListenerConfiguration);
+                }
+            }
+        }
+        else
+        {
+            expiryPolicyFactory = EternalExpiryPolicy.factoryOf();
+            storeByValue = true;
+            readThrough = false;
+            writeThrough = false;
+            statisticsEnabled = false;
+            managementEnabled = false;
+            cacheLoaderFactory = null;
+            cacheWristerFactory = null;
+            cacheEntryListenerConfigurations = new HashSet<>();
+        }
+    }
+
+    @Override
+    public Class<K> getKeyType()
+    {
+        return keyType == null ? (Class<K>) Object.class : keyType;
+    }
+
+    @Override
+    public Class<V> getValueType()
+    {
+        return valueType == null ? (Class<V>) Object.class : valueType;
+    }
+
+    @Override
+    public boolean isStoreByValue()
+    {
+        return storeByValue;
+    }
+
+    @Override
+    public boolean isReadThrough()
+    {
+        return readThrough;
+    }
+
+    @Override
+    public boolean isWriteThrough()
+    {
+        return writeThrough;
+    }
+
+    @Override
+    public boolean isStatisticsEnabled()
+    {
+        return statisticsEnabled;
+    }
+
+    @Override
+    public boolean isManagementEnabled()
+    {
+        return managementEnabled;
+    }
+
+    @Override
+    public Iterable<CacheEntryListenerConfiguration<K, V>> getCacheEntryListenerConfigurations()
+    {
+        return Collections.unmodifiableSet(cacheEntryListenerConfigurations);
+    }
+
+    @Override
+    public Factory<CacheLoader<K, V>> getCacheLoaderFactory()
+    {
+        return cacheLoaderFactory;
+    }
+
+    @Override
+    public Factory<CacheWriter<? super K, ? super V>> getCacheWriterFactory()
+    {
+        return cacheWristerFactory;
+    }
+
+    @Override
+    public Factory<ExpiryPolicy> getExpiryPolicyFactory()
+    {
+        return expiryPolicyFactory;
+    }
+
+    public synchronized void addListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration)
+    {
+        cacheEntryListenerConfigurations.add(cacheEntryListenerConfiguration);
+    }
+
+    public synchronized void removeListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration)
+    {
+        cacheEntryListenerConfigurations.remove(cacheEntryListenerConfiguration);
+    }
+
+    public void statisticsEnabled()
+    {
+        statisticsEnabled = true;
+    }
+
+    public void managementEnabled()
+    {
+        managementEnabled = true;
+    }
+
+    public void statisticsDisabled()
+    {
+        statisticsEnabled = false;
+    }
+
+    public void managementDisabled()
+    {
+        managementEnabled = false;
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/JCSEntry.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/JCSEntry.java
new file mode 100644
index 0000000..83e9d3e
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/JCSEntry.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache;
+
+import javax.cache.Cache;
+
+public class JCSEntry<K, V> implements Cache.Entry<K, V>
+{
+    private final K key;
+    private final V value;
+
+    public JCSEntry(final K key, final V value)
+    {
+        this.key = key;
+        this.value = value;
+    }
+
+    @Override
+    public K getKey()
+    {
+        return key;
+    }
+
+    @Override
+    public V getValue()
+    {
+        return value;
+    }
+
+    @Override
+    public <T> T unwrap(final Class<T> clazz)
+    {
+        if (clazz.isInstance(this))
+        {
+            return clazz.cast(this);
+        }
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/JCSListener.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/JCSListener.java
new file mode 100644
index 0000000..b0387e4
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/JCSListener.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache;
+
+import javax.cache.configuration.CacheEntryListenerConfiguration;
+import javax.cache.configuration.Factory;
+import javax.cache.event.CacheEntryCreatedListener;
+import javax.cache.event.CacheEntryEvent;
+import javax.cache.event.CacheEntryEventFilter;
+import javax.cache.event.CacheEntryExpiredListener;
+import javax.cache.event.CacheEntryListener;
+import javax.cache.event.CacheEntryListenerException;
+import javax.cache.event.CacheEntryRemovedListener;
+import javax.cache.event.CacheEntryUpdatedListener;
+import java.io.Closeable;
+import java.util.ArrayList;
+import java.util.List;
+
+public class JCSListener<K, V> implements Closeable
+{
+    private final boolean oldValue;
+    private final boolean synchronous;
+    private final CacheEntryEventFilter<? super K, ? super V> filter;
+    private final CacheEntryListener<? super K, ? super V> delegate;
+    private final boolean remove;
+    private final boolean expire;
+    private final boolean update;
+    private final boolean create;
+
+    public JCSListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration)
+    {
+        oldValue = cacheEntryListenerConfiguration.isOldValueRequired();
+        synchronous = cacheEntryListenerConfiguration.isSynchronous();
+
+        final Factory<CacheEntryEventFilter<? super K, ? super V>> filterFactory = cacheEntryListenerConfiguration
+                .getCacheEntryEventFilterFactory();
+        if (filterFactory == null)
+        {
+            filter = NoFilter.INSTANCE;
+        }
+        else
+        {
+            filter = filterFactory.create();
+        }
+
+        delegate = cacheEntryListenerConfiguration.getCacheEntryListenerFactory().create();
+        remove = CacheEntryRemovedListener.class.isInstance(delegate);
+        expire = CacheEntryExpiredListener.class.isInstance(delegate);
+        update = CacheEntryUpdatedListener.class.isInstance(delegate);
+        create = CacheEntryCreatedListener.class.isInstance(delegate);
+    }
+
+    public void onRemoved(final List<CacheEntryEvent<? extends K, ? extends V>> events) throws CacheEntryListenerException
+    {
+        if (remove)
+        {
+            CacheEntryRemovedListener.class.cast(delegate).onRemoved(filter(events));
+        }
+    }
+
+    public void onExpired(final List<CacheEntryEvent<? extends K, ? extends V>> events) throws CacheEntryListenerException
+    {
+        if (expire)
+        {
+            CacheEntryExpiredListener.class.cast(delegate).onExpired(filter(events));
+        }
+    }
+
+    public void onUpdated(final List<CacheEntryEvent<? extends K, ? extends V>> events) throws CacheEntryListenerException
+    {
+        if (update)
+        {
+            CacheEntryUpdatedListener.class.cast(delegate).onUpdated(filter(events));
+        }
+    }
+
+    public void onCreated(final List<CacheEntryEvent<? extends K, ? extends V>> events) throws CacheEntryListenerException
+    {
+        if (create)
+        {
+            CacheEntryCreatedListener.class.cast(delegate).onCreated(filter(events));
+        }
+    }
+
+    private Iterable<CacheEntryEvent<? extends K, ? extends V>> filter(final List<CacheEntryEvent<? extends K, ? extends V>> events)
+    {
+        if (filter == NoFilter.INSTANCE)
+        {
+            return events;
+        }
+
+        final List<CacheEntryEvent<? extends K, ? extends V>> filtered = new ArrayList<>(
+                events.size());
+        for (final CacheEntryEvent<? extends K, ? extends V> event : events)
+        {
+            if (filter.evaluate(event))
+            {
+                filtered.add(event);
+            }
+        }
+        return filtered;
+    }
+
+    @Override
+    public void close()
+    {
+        if (Closeable.class.isInstance(delegate)) {
+            Closeable.class.cast(delegate);
+        }
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/JCSMutableEntry.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/JCSMutableEntry.java
new file mode 100644
index 0000000..38c296a
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/JCSMutableEntry.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache;
+
+import javax.cache.Cache;
+import javax.cache.processor.MutableEntry;
+
+public class JCSMutableEntry<K, V> implements MutableEntry<K, V>
+{
+    private final Cache<K, V> cache;
+    private final K key;
+
+    public JCSMutableEntry(final Cache<K, V> cache, final K key)
+    {
+        this.cache = cache;
+        this.key = key;
+    }
+
+    @Override
+    public boolean exists()
+    {
+        return cache.containsKey(key);
+    }
+
+    @Override
+    public void remove()
+    {
+        cache.remove(key);
+    }
+
+    @Override
+    public void setValue(final V value)
+    {
+        cache.put(key, value);
+    }
+
+    @Override
+    public K getKey()
+    {
+        return key;
+    }
+
+    @Override
+    public V getValue()
+    {
+        return cache.get(key);
+    }
+
+    @Override
+    public <T> T unwrap(final Class<T> clazz)
+    {
+        if (clazz.isInstance(this))
+        {
+            return clazz.cast(this);
+        }
+        throw new IllegalArgumentException(clazz.getName() + " not supported in unwrap");
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/NoFilter.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/NoFilter.java
new file mode 100644
index 0000000..7d4a67b
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/NoFilter.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache;
+
+import javax.cache.event.CacheEntryEvent;
+import javax.cache.event.CacheEntryEventFilter;
+import javax.cache.event.CacheEntryListenerException;
+
+public class NoFilter implements CacheEntryEventFilter<Object, Object>
+{
+    public static final CacheEntryEventFilter<Object, Object> INSTANCE = new NoFilter();
+
+    private NoFilter()
+    {
+        // no-op
+    }
+
+    @Override
+    public boolean evaluate(final CacheEntryEvent<?, ?> event) throws CacheEntryListenerException
+    {
+        return true;
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/NoLoader.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/NoLoader.java
new file mode 100644
index 0000000..e3a1bbe
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/NoLoader.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache;
+
+import javax.cache.integration.CacheLoader;
+import javax.cache.integration.CacheLoaderException;
+import java.util.HashMap;
+import java.util.Map;
+
+public class NoLoader<K, V> implements CacheLoader<K, V>
+{
+    public static final NoLoader INSTANCE = new NoLoader();
+
+    private NoLoader()
+    {
+        // no-op
+    }
+
+    @Override
+    public V load(K key) throws CacheLoaderException
+    {
+        return null;
+    }
+
+    @Override
+    public Map<K, V> loadAll(final Iterable<? extends K> keys) throws CacheLoaderException
+    {
+        final Map<K, V> entries = new HashMap<>();
+        for (final K k : keys)
+        {
+            entries.put(k, null);
+        }
+        return entries;
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/NoWriter.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/NoWriter.java
new file mode 100644
index 0000000..939d3a6
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/NoWriter.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache;
+
+import javax.cache.Cache;
+import javax.cache.integration.CacheWriter;
+import javax.cache.integration.CacheWriterException;
+import java.util.Collection;
+
+public class NoWriter<K, V> implements CacheWriter<K, V>
+{
+    public static final NoWriter INSTANCE = new NoWriter();
+
+    @Override
+    public void write(final Cache.Entry<? extends K, ? extends V> entry) throws CacheWriterException
+    {
+        // no-op
+    }
+
+    @Override
+    public void delete(final Object key) throws CacheWriterException
+    {
+        // no-op
+    }
+
+    @Override
+    public void writeAll(final Collection<Cache.Entry<? extends K, ? extends V>> entries) throws CacheWriterException
+    {
+        for (final Cache.Entry<? extends K, ? extends V> entry : entries)
+        {
+            write(entry);
+        }
+    }
+
+    @Override
+    public void deleteAll(final Collection<?> keys) throws CacheWriterException
+    {
+        for (final Object k : keys)
+        {
+            delete(k);
+        }
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/Statistics.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/Statistics.java
new file mode 100644
index 0000000..91613e1
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/Statistics.java
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+public class Statistics
+{
+    private volatile boolean active = true;
+
+    private final AtomicLong removals = new AtomicLong();
+    private final AtomicLong expiries = new AtomicLong();
+    private final AtomicLong puts = new AtomicLong();
+    private final AtomicLong hits = new AtomicLong();
+    private final AtomicLong misses = new AtomicLong();
+    private final AtomicLong evictions = new AtomicLong();
+    private final AtomicLong putTimeTaken = new AtomicLong();
+    private final AtomicLong getTimeTaken = new AtomicLong();
+    private final AtomicLong removeTimeTaken = new AtomicLong();
+
+    public long getHits()
+    {
+        return hits.get();
+    }
+
+    public long getMisses()
+    {
+        return misses.get();
+    }
+
+    public long getPuts()
+    {
+        return puts.get();
+    }
+
+    public long getRemovals()
+    {
+        return removals.get();
+    }
+
+    public long getEvictions()
+    {
+        return evictions.get();
+    }
+
+    public long getTimeTakenForGets()
+    {
+        return getTimeTaken.get();
+    }
+
+    public long getTimeTakenForPuts()
+    {
+        return putTimeTaken.get();
+    }
+
+    public long getTimeTakenForRemovals()
+    {
+        return removeTimeTaken.get();
+    }
+
+    public void increaseRemovals(final long number)
+    {
+        increment(removals, number);
+    }
+
+    public void increaseExpiries(final long number)
+    {
+        increment(expiries, number);
+    }
+
+    public void increasePuts(final long number)
+    {
+        increment(puts, number);
+    }
+
+    public void increaseHits(final long number)
+    {
+        increment(hits, number);
+    }
+
+    public void increaseMisses(final long number)
+    {
+        increment(misses, number);
+    }
+
+    public void increaseEvictions(final long number)
+    {
+        increment(evictions, number);
+    }
+
+    public void addGetTime(final long duration)
+    {
+        increment(duration, getTimeTaken);
+    }
+
+    public void addPutTime(final long duration)
+    {
+        increment(duration, putTimeTaken);
+    }
+
+    public void addRemoveTime(final long duration)
+    {
+        increment(duration, removeTimeTaken);
+    }
+
+    private void increment(final AtomicLong counter, final long number)
+    {
+        if (!active)
+        {
+            return;
+        }
+        counter.addAndGet(number);
+    }
+
+    private void increment(final long duration, final AtomicLong counter)
+    {
+        if (!active)
+        {
+            return;
+        }
+
+        if (counter.get() < Long.MAX_VALUE - duration)
+        {
+            counter.addAndGet(duration);
+        }
+        else
+        {
+            reset();
+            counter.set(duration);
+        }
+    }
+
+    public void reset()
+    {
+        puts.set(0);
+        misses.set(0);
+        removals.set(0);
+        expiries.set(0);
+        hits.set(0);
+        evictions.set(0);
+        getTimeTaken.set(0);
+        putTimeTaken.set(0);
+        removeTimeTaken.set(0);
+    }
+
+    public void setActive(final boolean active)
+    {
+        this.active = active;
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/TempStateCacheView.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/TempStateCacheView.java
new file mode 100644
index 0000000..f3436c3
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/TempStateCacheView.java
@@ -0,0 +1,352 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache;
+
+import javax.cache.Cache;
+import javax.cache.CacheManager;
+import javax.cache.configuration.CacheEntryListenerConfiguration;
+import javax.cache.configuration.CompleteConfiguration;
+import javax.cache.configuration.Configuration;
+import javax.cache.integration.CompletionListener;
+import javax.cache.processor.EntryProcessor;
+import javax.cache.processor.EntryProcessorException;
+import javax.cache.processor.EntryProcessorResult;
+
+import static org.apache.commons.jcs3.jcache.Asserts.assertNotNull;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+
+// kind of transactional view for a Cache<K, V>, to use with EntryProcessor
+public class TempStateCacheView<K, V> implements Cache<K, V>
+{
+    private final JCSCache<K, V> cache;
+    private final Map<K, V> put = new HashMap<>();
+    private final Collection<K> remove = new LinkedList<>();
+    private boolean removeAll = false;
+    private boolean clear = false;
+
+    public TempStateCacheView(final JCSCache<K, V> entries)
+    {
+        this.cache = entries;
+    }
+
+    @Override
+    public V get(final K key)
+    {
+        if (ignoreKey(key))
+        {
+            return null;
+        }
+
+        final V v = put.get(key);
+        if (v != null)
+        {
+            return v;
+        }
+
+        // for an EntryProcessor we already incremented stats - to enhance
+        // surely
+        if (cache.getConfiguration(CompleteConfiguration.class).isStatisticsEnabled())
+        {
+            final Statistics statistics = cache.getStatistics();
+            if (cache.containsKey(key))
+            {
+                statistics.increaseHits(-1);
+            }
+            else
+            {
+                statistics.increaseMisses(-1);
+            }
+        }
+        return cache.get(key);
+    }
+
+    private boolean ignoreKey(final K key)
+    {
+        return removeAll || clear || remove.contains(key);
+    }
+
+    @Override
+    public Map<K, V> getAll(final Set<? extends K> keys)
+    {
+        final Map<K, V> v = new HashMap<>(keys.size());
+        final Set<K> missing = new HashSet<>();
+        for (final K k : keys)
+        {
+            final V value = put.get(k);
+            if (value != null)
+            {
+                v.put(k, value);
+            }
+            else if (!ignoreKey(k))
+            {
+                missing.add(k);
+            }
+        }
+        if (!missing.isEmpty())
+        {
+            v.putAll(cache.getAll(missing));
+        }
+        return v;
+    }
+
+    @Override
+    public boolean containsKey(final K key)
+    {
+        return !ignoreKey(key) && (put.containsKey(key) || cache.containsKey(key));
+    }
+
+    @Override
+    public void loadAll(final Set<? extends K> keys, final boolean replaceExistingValues, final CompletionListener completionListener)
+    {
+        cache.loadAll(keys, replaceExistingValues, completionListener);
+    }
+
+    @Override
+    public void put(final K key, final V value)
+    {
+        assertNotNull(key, "key");
+        assertNotNull(value, "value");
+        put.put(key, value);
+        remove.remove(key);
+    }
+
+    @Override
+    public V getAndPut(final K key, final V value)
+    {
+        final V v = get(key);
+        put(key, value);
+        return v;
+    }
+
+    @Override
+    public void putAll(final Map<? extends K, ? extends V> map)
+    {
+        put.putAll(map);
+        for (final K k : map.keySet())
+        {
+            remove.remove(k);
+        }
+    }
+
+    @Override
+    public boolean putIfAbsent(final K key, final V value)
+    {
+        if (!put.containsKey(key))
+        {
+            put.put(key, value);
+            remove.remove(key);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean remove(final K key)
+    {
+        final boolean noop = put.containsKey(key);
+        put.remove(key);
+        if (!ignoreKey(key))
+        {
+            if (!noop)
+            {
+                remove.add(key);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean remove(final K key, final V oldValue)
+    {
+        put.remove(key);
+        if (!ignoreKey(key) && oldValue.equals(cache.get(key)))
+        {
+            remove.add(key);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public V getAndRemove(final K key)
+    {
+        final V v = get(key);
+        remove.add(key);
+        put.remove(key);
+        return v;
+    }
+
+    @Override
+    public boolean replace(final K key, final V oldValue, final V newValue)
+    {
+        if (oldValue.equals(get(key)))
+        {
+            put(key, newValue);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean replace(final K key, final V value)
+    {
+        if (containsKey(key))
+        {
+            remove(key);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public V getAndReplace(final K key, final V value)
+    {
+        if (containsKey(key))
+        {
+            final V oldValue = get(key);
+            put(key, value);
+            return oldValue;
+        }
+        return null;
+    }
+
+    @Override
+    public void removeAll(final Set<? extends K> keys)
+    {
+        remove.addAll(keys);
+        for (final K k : keys)
+        {
+            put.remove(k);
+        }
+    }
+
+    @Override
+    public void removeAll()
+    {
+        removeAll = true;
+        put.clear();
+        remove.clear();
+    }
+
+    @Override
+    public void clear()
+    {
+        clear = true;
+        put.clear();
+        remove.clear();
+    }
+
+    @Override
+    public <C extends Configuration<K, V>> C getConfiguration(final Class<C> clazz)
+    {
+        return cache.getConfiguration(clazz);
+    }
+
+    @Override
+    public <T> T invoke(final K key, final EntryProcessor<K, V, T> entryProcessor, final Object... arguments) throws EntryProcessorException
+    {
+        return cache.invoke(key, entryProcessor, arguments);
+    }
+
+    @Override
+    public <T> Map<K, EntryProcessorResult<T>> invokeAll(Set<? extends K> keys, final EntryProcessor<K, V, T> entryProcessor,
+            final Object... arguments)
+    {
+        return cache.invokeAll(keys, entryProcessor, arguments);
+    }
+
+    @Override
+    public String getName()
+    {
+        return cache.getName();
+    }
+
+    @Override
+    public CacheManager getCacheManager()
+    {
+        return cache.getCacheManager();
+    }
+
+    @Override
+    public void close()
+    {
+        cache.close();
+    }
+
+    @Override
+    public boolean isClosed()
+    {
+        return cache.isClosed();
+    }
+
+    @Override
+    public <T> T unwrap(final Class<T> clazz)
+    {
+        return cache.unwrap(clazz);
+    }
+
+    @Override
+    public void registerCacheEntryListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration)
+    {
+        cache.registerCacheEntryListener(cacheEntryListenerConfiguration);
+    }
+
+    @Override
+    public void deregisterCacheEntryListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration)
+    {
+        cache.deregisterCacheEntryListener(cacheEntryListenerConfiguration);
+    }
+
+    @Override
+    public Iterator<Entry<K, V>> iterator()
+    {
+        return cache.iterator();
+    }
+
+    public void merge()
+    {
+        if (removeAll)
+        {
+            cache.removeAll();
+        }
+        if (clear)
+        {
+            cache.clear();
+        }
+
+        for (final Map.Entry<K, V> entry : put.entrySet())
+        {
+            cache.put(entry.getKey(), entry.getValue());
+        }
+        put.clear();
+        for (final K entry : remove)
+        {
+            cache.remove(entry);
+        }
+        remove.clear();
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/Times.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/Times.java
new file mode 100644
index 0000000..26c22d1
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/Times.java
@@ -0,0 +1,37 @@
+package org.apache.commons.jcs3.jcache;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+public class Times
+{
+    public static long now(final boolean ignore)
+    {
+        if (ignore)
+        {
+            return -1;
+        }
+        return System.nanoTime() / 1000;
+    }
+
+    private Times()
+    {
+        // no-op
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CDIJCacheHelper.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CDIJCacheHelper.java
new file mode 100644
index 0000000..6a5eab8
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CDIJCacheHelper.java
@@ -0,0 +1,670 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.cdi;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.logging.Logger;
+
+import javax.annotation.PreDestroy;
+import javax.cache.annotation.CacheDefaults;
+import javax.cache.annotation.CacheKey;
+import javax.cache.annotation.CacheKeyGenerator;
+import javax.cache.annotation.CachePut;
+import javax.cache.annotation.CacheRemove;
+import javax.cache.annotation.CacheRemoveAll;
+import javax.cache.annotation.CacheResolverFactory;
+import javax.cache.annotation.CacheResult;
+import javax.cache.annotation.CacheValue;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.inject.Inject;
+import javax.interceptor.InvocationContext;
+
+@ApplicationScoped
+public class CDIJCacheHelper
+{
+    private static final Logger LOGGER = Logger.getLogger(CDIJCacheHelper.class.getName());
+    private static final boolean CLOSE_CACHE = !Boolean.getBoolean("org.apache.commons.jcs3.jcache.cdi.skip-close");
+
+    private volatile CacheResolverFactoryImpl defaultCacheResolverFactory = null; // lazy to not create any cache if not needed
+    private final CacheKeyGeneratorImpl defaultCacheKeyGenerator = new CacheKeyGeneratorImpl();
+
+    private final Collection<CreationalContext<?>> toRelease = new ArrayList<>();
+    private final ConcurrentMap<MethodKey, MethodMeta> methods = new ConcurrentHashMap<>();
+
+    @Inject
+    private BeanManager beanManager;
+
+    @PreDestroy
+    private void release() {
+        if (CLOSE_CACHE && defaultCacheResolverFactory != null)
+        {
+            defaultCacheResolverFactory.release();
+        }
+        for (final CreationalContext<?> cc : toRelease)
+        {
+            try
+            {
+                cc.release();
+            }
+            catch (final RuntimeException re)
+            {
+                LOGGER.warning(re.getMessage());
+            }
+        }
+    }
+
+    public MethodMeta findMeta(final InvocationContext ic)
+    {
+        final Method mtd = ic.getMethod();
+        final Class<?> refType = findKeyType(ic.getTarget());
+        final MethodKey key = new MethodKey(refType, mtd);
+        MethodMeta methodMeta = methods.get(key);
+        if (methodMeta == null)
+        {
+            synchronized (this)
+            {
+                methodMeta = methods.get(key);
+                if (methodMeta == null)
+                {
+                    methodMeta = createMeta(ic);
+                    methods.put(key, methodMeta);
+                }
+            }
+        }
+        return methodMeta;
+    }
+
+    private Class<?> findKeyType(final Object target)
+    {
+        if (null == target)
+        {
+            return null;
+        }
+        return target.getClass();
+    }
+
+    // it is unlikely we have all annotations but for now we have a single meta model
+    private MethodMeta createMeta(final InvocationContext ic)
+    {
+        final CacheDefaults defaults = findDefaults(ic.getTarget() == null ? null : ic.getTarget()
+                                                      .getClass(), ic.getMethod());
+
+        final Class<?>[] parameterTypes = ic.getMethod().getParameterTypes();
+        final Annotation[][] parameterAnnotations = ic.getMethod().getParameterAnnotations();
+        final List<Set<Annotation>> annotations = new ArrayList<>();
+        for (final Annotation[] parameterAnnotation : parameterAnnotations)
+        {
+            final Set<Annotation> set = new HashSet<>(parameterAnnotation.length);
+            set.addAll(Arrays.asList(parameterAnnotation));
+            annotations.add(set);
+        }
+
+        final Set<Annotation> mtdAnnotations = new HashSet<>();
+        mtdAnnotations.addAll(Arrays.asList(ic.getMethod().getAnnotations()));
+
+        final CacheResult cacheResult = ic.getMethod().getAnnotation(CacheResult.class);
+        final String cacheResultCacheResultName = cacheResult == null ? null : defaultName(ic.getMethod(), defaults, cacheResult.cacheName());
+        final CacheResolverFactory cacheResultCacheResolverFactory = cacheResult == null ?
+                null : cacheResolverFactoryFor(defaults, cacheResult.cacheResolverFactory());
+        final CacheKeyGenerator cacheResultCacheKeyGenerator = cacheResult == null ?
+                null : cacheKeyGeneratorFor(defaults, cacheResult.cacheKeyGenerator());
+
+        final CachePut cachePut = ic.getMethod().getAnnotation(CachePut.class);
+        final String cachePutCachePutName = cachePut == null ? null : defaultName(ic.getMethod(), defaults, cachePut.cacheName());
+        final CacheResolverFactory cachePutCacheResolverFactory = cachePut == null ?
+                null : cacheResolverFactoryFor(defaults, cachePut.cacheResolverFactory());
+        final CacheKeyGenerator cachePutCacheKeyGenerator = cachePut == null ?
+                null : cacheKeyGeneratorFor(defaults, cachePut.cacheKeyGenerator());
+
+        final CacheRemove cacheRemove = ic.getMethod().getAnnotation(CacheRemove.class);
+        final String cacheRemoveCacheRemoveName = cacheRemove == null ? null : defaultName(ic.getMethod(), defaults, cacheRemove.cacheName());
+        final CacheResolverFactory cacheRemoveCacheResolverFactory = cacheRemove == null ?
+                null : cacheResolverFactoryFor(defaults, cacheRemove.cacheResolverFactory());
+        final CacheKeyGenerator cacheRemoveCacheKeyGenerator = cacheRemove == null ?
+                null : cacheKeyGeneratorFor(defaults, cacheRemove.cacheKeyGenerator());
+
+        final CacheRemoveAll cacheRemoveAll = ic.getMethod().getAnnotation(CacheRemoveAll.class);
+        final String cacheRemoveAllCacheRemoveAllName = cacheRemoveAll == null ? null : defaultName(ic.getMethod(), defaults, cacheRemoveAll.cacheName());
+        final CacheResolverFactory cacheRemoveAllCacheResolverFactory = cacheRemoveAll == null ?
+                null : cacheResolverFactoryFor(defaults, cacheRemoveAll.cacheResolverFactory());
+
+        return new MethodMeta(
+                parameterTypes,
+                annotations,
+                mtdAnnotations,
+                keyParameterIndexes(ic.getMethod()),
+                getValueParameter(annotations),
+                getKeyParameters(annotations),
+                cacheResultCacheResultName,
+                cacheResultCacheResolverFactory,
+                cacheResultCacheKeyGenerator,
+                cacheResult,
+                cachePutCachePutName,
+                cachePutCacheResolverFactory,
+                cachePutCacheKeyGenerator,
+                cachePut != null && cachePut.afterInvocation(),
+                cachePut,
+                cacheRemoveCacheRemoveName,
+                cacheRemoveCacheResolverFactory,
+                cacheRemoveCacheKeyGenerator,
+                cacheRemove != null && cacheRemove.afterInvocation(),
+                cacheRemove,
+                cacheRemoveAllCacheRemoveAllName,
+                cacheRemoveAllCacheResolverFactory,
+                cacheRemoveAll != null && cacheRemoveAll.afterInvocation(),
+                cacheRemoveAll);
+    }
+
+    private Integer[] getKeyParameters(final List<Set<Annotation>> annotations)
+    {
+        final Collection<Integer> list = new ArrayList<>();
+        int idx = 0;
+        for (final Set<Annotation> set : annotations)
+        {
+            for (final Annotation a : set)
+            {
+                if (a.annotationType() == CacheKey.class)
+                {
+                    list.add(idx);
+                }
+            }
+            idx++;
+        }
+        if (list.isEmpty())
+        {
+            for (int i = 0; i < annotations.size(); i++)
+            {
+                list.add(i);
+            }
+        }
+        return list.toArray(new Integer[list.size()]);
+    }
+
+    private Integer getValueParameter(final List<Set<Annotation>> annotations)
+    {
+        int idx = 0;
+        for (final Set<Annotation> set : annotations)
+        {
+            for (final Annotation a : set)
+            {
+                if (a.annotationType() == CacheValue.class)
+                {
+                    return idx;
+                }
+            }
+        }
+        return -1;
+    }
+
+    private String defaultName(final Method method, final CacheDefaults defaults, final String cacheName)
+    {
+        if (!cacheName.isEmpty())
+        {
+            return cacheName;
+        }
+        if (defaults != null)
+        {
+            final String name = defaults.cacheName();
+            if (!name.isEmpty())
+            {
+                return name;
+            }
+        }
+
+        final StringBuilder name = new StringBuilder(method.getDeclaringClass().getName());
+        name.append(".");
+        name.append(method.getName());
+        name.append("(");
+        final Class<?>[] parameterTypes = method.getParameterTypes();
+        for (int pIdx = 0; pIdx < parameterTypes.length; pIdx++)
+        {
+            name.append(parameterTypes[pIdx].getName());
+            if ((pIdx + 1) < parameterTypes.length)
+            {
+                name.append(",");
+            }
+        }
+        name.append(")");
+        return name.toString();
+    }
+
+    private CacheDefaults findDefaults(final Class<?> targetType, final Method method)
+    {
+        if (Proxy.isProxyClass(targetType)) // target doesnt hold annotations
+        {
+            final Class<?> api = method.getDeclaringClass();
+            for (final Class<?> type : targetType
+                                         .getInterfaces())
+            {
+                if (!api.isAssignableFrom(type))
+                {
+                    continue;
+                }
+                return extractDefaults(type);
+            }
+        }
+        return extractDefaults(targetType);
+    }
+
+    private CacheDefaults extractDefaults(final Class<?> type)
+    {
+        CacheDefaults annotation = null;
+        Class<?> clazz = type;
+        while (clazz != null && clazz != Object.class)
+        {
+            annotation = clazz.getAnnotation(CacheDefaults.class);
+            if (annotation != null)
+            {
+                break;
+            }
+            clazz = clazz.getSuperclass();
+        }
+        return annotation;
+    }
+
+    public boolean isIncluded(final Class<?> aClass, final Class<?>[] in, final Class<?>[] out)
+    {
+        if (in.length == 0 && out.length == 0)
+        {
+            return false;
+        }
+        for (final Class<?> potentialIn : in)
+        {
+            if (potentialIn.isAssignableFrom(aClass))
+            {
+                for (final Class<?> potentialOut : out)
+                {
+                    if (potentialOut.isAssignableFrom(aClass))
+                    {
+                        return false;
+                    }
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private CacheKeyGenerator cacheKeyGeneratorFor(final CacheDefaults defaults, final Class<? extends CacheKeyGenerator> cacheKeyGenerator)
+    {
+        if (!CacheKeyGenerator.class.equals(cacheKeyGenerator))
+        {
+            return instance(cacheKeyGenerator);
+        }
+        if (defaults != null)
+        {
+            final Class<? extends CacheKeyGenerator> defaultCacheKeyGenerator = defaults.cacheKeyGenerator();
+            if (!CacheKeyGenerator.class.equals(defaultCacheKeyGenerator))
+            {
+                return instance(defaultCacheKeyGenerator);
+            }
+        }
+        return defaultCacheKeyGenerator;
+    }
+
+    private CacheResolverFactory cacheResolverFactoryFor(final CacheDefaults defaults, final Class<? extends CacheResolverFactory> cacheResolverFactory)
+    {
+        if (!CacheResolverFactory.class.equals(cacheResolverFactory))
+        {
+            return instance(cacheResolverFactory);
+        }
+        if (defaults != null)
+        {
+            final Class<? extends CacheResolverFactory> defaultCacheResolverFactory = defaults.cacheResolverFactory();
+            if (!CacheResolverFactory.class.equals(defaultCacheResolverFactory))
+            {
+                return instance(defaultCacheResolverFactory);
+            }
+        }
+        return defaultCacheResolverFactory();
+    }
+
+    private <T> T instance(final Class<T> type)
+    {
+        final Set<Bean<?>> beans = beanManager.getBeans(type);
+        if (beans.isEmpty())
+        {
+            if (CacheKeyGenerator.class == type) {
+                return (T) defaultCacheKeyGenerator;
+            }
+            if (CacheResolverFactory.class == type) {
+                return (T) defaultCacheResolverFactory();
+            }
+            return null;
+        }
+        final Bean<?> bean = beanManager.resolve(beans);
+        final CreationalContext<?> context = beanManager.createCreationalContext(bean);
+        final Class<? extends Annotation> scope = bean.getScope();
+        final boolean normalScope = beanManager.isNormalScope(scope);
+        try
+        {
+            final Object reference = beanManager.getReference(bean, bean.getBeanClass(), context);
+            if (!normalScope)
+            {
+                toRelease.add(context);
+            }
+            return (T) reference;
+        }
+        finally
+        {
+            if (normalScope)
+            { // TODO: release at the right moment, @PreDestroy? question is: do we assume it is thread safe?
+                context.release();
+            }
+        }
+    }
+
+    private CacheResolverFactoryImpl defaultCacheResolverFactory()
+    {
+        if (defaultCacheResolverFactory != null) {
+            return defaultCacheResolverFactory;
+        }
+        synchronized (this) {
+            if (defaultCacheResolverFactory != null) {
+                return defaultCacheResolverFactory;
+            }
+            defaultCacheResolverFactory = new CacheResolverFactoryImpl();
+        }
+        return defaultCacheResolverFactory;
+    }
+
+    private Integer[] keyParameterIndexes(final Method method)
+    {
+        final List<Integer> keys = new LinkedList<>();
+        final Annotation[][] parameterAnnotations = method.getParameterAnnotations();
+
+        // first check if keys are specified explicitely
+        for (int i = 0; i < method.getParameterTypes().length; i++)
+        {
+            final Annotation[] annotations = parameterAnnotations[i];
+            for (final Annotation a : annotations)
+            {
+                if (a.annotationType().equals(CacheKey.class))
+                {
+                    keys.add(i);
+                    break;
+                }
+            }
+        }
+
+        // if not then use all parameters but value ones
+        if (keys.isEmpty())
+        {
+            for (int i = 0; i < method.getParameterTypes().length; i++)
+            {
+                final Annotation[] annotations = parameterAnnotations[i];
+                boolean value = false;
+                for (final Annotation a : annotations)
+                {
+                    if (a.annotationType().equals(CacheValue.class))
+                    {
+                        value = true;
+                        break;
+                    }
+                }
+                if (!value) {
+                    keys.add(i);
+                }
+            }
+        }
+        return keys.toArray(new Integer[keys.size()]);
+    }
+
+    private static final class MethodKey
+    {
+        private final Class<?> base;
+        private final Method delegate;
+        private final int hash;
+
+        private MethodKey(final Class<?> base, final Method delegate)
+        {
+            this.base = base; // we need a class to ensure inheritance don't fall in the same key
+            this.delegate = delegate;
+            this.hash = 31 * delegate.hashCode() + (base == null ? 0 : base.hashCode());
+        }
+
+        @Override
+        public boolean equals(final Object o)
+        {
+            if (this == o)
+            {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass())
+            {
+                return false;
+            }
+            final MethodKey classKey = MethodKey.class.cast(o);
+            return delegate.equals(classKey.delegate) && ((base == null && classKey.base == null) || (base != null && base.equals(classKey.base)));
+        }
+
+        @Override
+        public int hashCode()
+        {
+            return hash;
+        }
+    }
+
+    // TODO: split it in 5?
+    public static class MethodMeta
+    {
+        private final Class<?>[] parameterTypes;
+        private final List<Set<Annotation>> parameterAnnotations;
+        private final Set<Annotation> annotations;
+        private final Integer[] keysIndices;
+        private final Integer valueIndex;
+        private final Integer[] parameterIndices;
+
+        private final String cacheResultCacheName;
+        private final CacheResolverFactory cacheResultResolverFactory;
+        private final CacheKeyGenerator cacheResultKeyGenerator;
+        private final CacheResult cacheResult;
+
+        private final String cachePutCacheName;
+        private final CacheResolverFactory cachePutResolverFactory;
+        private final CacheKeyGenerator cachePutKeyGenerator;
+        private final boolean cachePutAfter;
+        private final CachePut cachePut;
+
+        private final String cacheRemoveCacheName;
+        private final CacheResolverFactory cacheRemoveResolverFactory;
+        private final CacheKeyGenerator cacheRemoveKeyGenerator;
+        private final boolean cacheRemoveAfter;
+        private final CacheRemove cacheRemove;
+
+        private final String cacheRemoveAllCacheName;
+        private final CacheResolverFactory cacheRemoveAllResolverFactory;
+        private final boolean cacheRemoveAllAfter;
+        private final CacheRemoveAll cacheRemoveAll;
+
+        public MethodMeta(Class<?>[] parameterTypes, List<Set<Annotation>> parameterAnnotations, Set<Annotation>
+                annotations, Integer[] keysIndices, Integer valueIndex, Integer[] parameterIndices, String
+                cacheResultCacheName, CacheResolverFactory cacheResultResolverFactory, CacheKeyGenerator
+                cacheResultKeyGenerator, CacheResult cacheResult, String cachePutCacheName, CacheResolverFactory
+                cachePutResolverFactory, CacheKeyGenerator cachePutKeyGenerator, boolean cachePutAfter, CachePut cachePut, String
+                cacheRemoveCacheName, CacheResolverFactory cacheRemoveResolverFactory, CacheKeyGenerator
+                cacheRemoveKeyGenerator, boolean cacheRemoveAfter, CacheRemove cacheRemove, String cacheRemoveAllCacheName,
+                          CacheResolverFactory cacheRemoveAllResolverFactory, boolean
+                                  cacheRemoveAllAfter, CacheRemoveAll cacheRemoveAll)
+        {
+            this.parameterTypes = parameterTypes;
+            this.parameterAnnotations = parameterAnnotations;
+            this.annotations = annotations;
+            this.keysIndices = keysIndices;
+            this.valueIndex = valueIndex;
+            this.parameterIndices = parameterIndices;
+            this.cacheResultCacheName = cacheResultCacheName;
+            this.cacheResultResolverFactory = cacheResultResolverFactory;
+            this.cacheResultKeyGenerator = cacheResultKeyGenerator;
+            this.cacheResult = cacheResult;
+            this.cachePutCacheName = cachePutCacheName;
+            this.cachePutResolverFactory = cachePutResolverFactory;
+            this.cachePutKeyGenerator = cachePutKeyGenerator;
+            this.cachePutAfter = cachePutAfter;
+            this.cachePut = cachePut;
+            this.cacheRemoveCacheName = cacheRemoveCacheName;
+            this.cacheRemoveResolverFactory = cacheRemoveResolverFactory;
+            this.cacheRemoveKeyGenerator = cacheRemoveKeyGenerator;
+            this.cacheRemoveAfter = cacheRemoveAfter;
+            this.cacheRemove = cacheRemove;
+            this.cacheRemoveAllCacheName = cacheRemoveAllCacheName;
+            this.cacheRemoveAllResolverFactory = cacheRemoveAllResolverFactory;
+            this.cacheRemoveAllAfter = cacheRemoveAllAfter;
+            this.cacheRemoveAll = cacheRemoveAll;
+        }
+
+        public boolean isCacheRemoveAfter()
+        {
+            return cacheRemoveAfter;
+        }
+
+        public boolean isCachePutAfter()
+        {
+            return cachePutAfter;
+        }
+
+        public Class<?>[] getParameterTypes()
+        {
+            return parameterTypes;
+        }
+
+        public List<Set<Annotation>> getParameterAnnotations()
+        {
+            return parameterAnnotations;
+        }
+
+        public String getCacheResultCacheName()
+        {
+            return cacheResultCacheName;
+        }
+
+        public CacheResolverFactory getCacheResultResolverFactory()
+        {
+            return cacheResultResolverFactory;
+        }
+
+        public CacheKeyGenerator getCacheResultKeyGenerator()
+        {
+            return cacheResultKeyGenerator;
+        }
+
+        public CacheResult getCacheResult() {
+            return cacheResult;
+        }
+
+        public Integer[] getParameterIndices()
+        {
+            return parameterIndices;
+        }
+
+        public Set<Annotation> getAnnotations()
+        {
+            return annotations;
+        }
+
+        public Integer[] getKeysIndices()
+        {
+            return keysIndices;
+        }
+
+        public Integer getValuesIndex()
+        {
+            return valueIndex;
+        }
+
+        public Integer getValueIndex()
+        {
+            return valueIndex;
+        }
+
+        public String getCachePutCacheName()
+        {
+            return cachePutCacheName;
+        }
+
+        public CacheResolverFactory getCachePutResolverFactory()
+        {
+            return cachePutResolverFactory;
+        }
+
+        public CacheKeyGenerator getCachePutKeyGenerator()
+        {
+            return cachePutKeyGenerator;
+        }
+
+        public CachePut getCachePut()
+        {
+            return cachePut;
+        }
+
+        public String getCacheRemoveCacheName()
+        {
+            return cacheRemoveCacheName;
+        }
+
+        public CacheResolverFactory getCacheRemoveResolverFactory()
+        {
+            return cacheRemoveResolverFactory;
+        }
+
+        public CacheKeyGenerator getCacheRemoveKeyGenerator()
+        {
+            return cacheRemoveKeyGenerator;
+        }
+
+        public CacheRemove getCacheRemove()
+        {
+            return cacheRemove;
+        }
+
+        public String getCacheRemoveAllCacheName()
+        {
+            return cacheRemoveAllCacheName;
+        }
+
+        public CacheResolverFactory getCacheRemoveAllResolverFactory()
+        {
+            return cacheRemoveAllResolverFactory;
+        }
+
+        public boolean isCacheRemoveAllAfter()
+        {
+            return cacheRemoveAllAfter;
+        }
+
+        public CacheRemoveAll getCacheRemoveAll()
+        {
+            return cacheRemoveAll;
+        }
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CacheInvocationContextImpl.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CacheInvocationContextImpl.java
new file mode 100644
index 0000000..4ebcc78
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CacheInvocationContextImpl.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.cdi;
+
+import java.lang.annotation.Annotation;
+import java.util.List;
+import java.util.Set;
+
+import javax.cache.annotation.CacheInvocationContext;
+import javax.cache.annotation.CacheInvocationParameter;
+import javax.interceptor.InvocationContext;
+
+public class CacheInvocationContextImpl<A extends Annotation> extends CacheMethodDetailsImpl<A> implements CacheInvocationContext<A>
+{
+    private static final Object[] EMPTY_ARGS = new Object[0];
+
+    private CacheInvocationParameter[] parameters = null;
+
+    public CacheInvocationContextImpl(final InvocationContext delegate, final A cacheAnnotation, final String cacheName,
+                                      final CDIJCacheHelper.MethodMeta meta)
+    {
+        super(delegate, cacheAnnotation, cacheName, meta);
+    }
+
+    @Override
+    public Object getTarget()
+    {
+        return delegate.getTarget();
+    }
+
+    @Override
+    public CacheInvocationParameter[] getAllParameters()
+    {
+        if (parameters == null)
+        {
+            parameters = doGetAllParameters(null);
+        }
+        return parameters;
+    }
+
+    @Override
+    public <T> T unwrap(final Class<T> cls)
+    {
+        if (cls.isAssignableFrom(getClass()))
+        {
+            return cls.cast(this);
+        }
+        throw new IllegalArgumentException(cls.getName());
+    }
+
+    protected CacheInvocationParameter[] doGetAllParameters(final Integer[] indexes)
+    {
+        final Object[] parameters = delegate.getParameters();
+        final Object[] args = parameters == null ? EMPTY_ARGS : parameters;
+        final Class<?>[] parameterTypes = meta.getParameterTypes();
+        final List<Set<Annotation>> parameterAnnotations = meta.getParameterAnnotations();
+
+        final CacheInvocationParameter[] parametersAsArray = new CacheInvocationParameter[indexes == null ? args.length : indexes.length];
+        if (indexes == null)
+        {
+            for (int i = 0; i < args.length; i++)
+            {
+                parametersAsArray[i] = newCacheInvocationParameterImpl(parameterTypes[i], args[i], parameterAnnotations.get(i), i);
+            }
+        }
+        else
+        {
+            int outIdx = 0;
+            for (int idx = 0; idx < indexes.length; idx++)
+            {
+                final int i = indexes[idx];
+                parametersAsArray[outIdx] = newCacheInvocationParameterImpl(parameterTypes[i], args[i], parameterAnnotations.get(i), i);
+                outIdx++;
+            }
+        }
+        return parametersAsArray;
+    }
+
+    private CacheInvocationParameterImpl newCacheInvocationParameterImpl(final Class<?> type, final Object arg,
+                                                                         final Set<Annotation> annotations, final int i) {
+        return new CacheInvocationParameterImpl(type, arg, annotations, i);
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CacheInvocationParameterImpl.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CacheInvocationParameterImpl.java
new file mode 100644
index 0000000..4d5caa5
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CacheInvocationParameterImpl.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.cdi;
+
+import javax.cache.annotation.CacheInvocationParameter;
+import java.lang.annotation.Annotation;
+import java.util.Set;
+
+public class CacheInvocationParameterImpl implements CacheInvocationParameter
+{
+    private final Class<?> type;
+    private final Object value;
+    private final Set<Annotation> annotations;
+    private final int position;
+
+    public CacheInvocationParameterImpl(final Class<?> type, final Object value, final Set<Annotation> annotations, final int position)
+    {
+        this.type = type;
+        this.value = value;
+        this.annotations = annotations;
+        this.position = position;
+    }
+
+    @Override
+    public Class<?> getRawType()
+    {
+        return type;
+    }
+
+    @Override
+    public Object getValue()
+    {
+        return value;
+    }
+
+    @Override
+    public Set<Annotation> getAnnotations()
+    {
+        return annotations;
+    }
+
+    @Override
+    public int getParameterPosition()
+    {
+        return position;
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CacheKeyGeneratorImpl.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CacheKeyGeneratorImpl.java
new file mode 100644
index 0000000..10e0a41
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CacheKeyGeneratorImpl.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.cdi;
+
+import javax.cache.annotation.CacheInvocationParameter;
+import javax.cache.annotation.CacheKeyGenerator;
+import javax.cache.annotation.CacheKeyInvocationContext;
+import javax.cache.annotation.GeneratedCacheKey;
+import java.lang.annotation.Annotation;
+
+public class CacheKeyGeneratorImpl implements CacheKeyGenerator
+{
+    @Override
+    public GeneratedCacheKey generateCacheKey(final CacheKeyInvocationContext<? extends Annotation> cacheKeyInvocationContext)
+    {
+        final CacheInvocationParameter[] keyParameters = cacheKeyInvocationContext.getKeyParameters();
+        final Object[] parameters = new Object[keyParameters.length];
+        for (int index = 0; index < keyParameters.length; index++)
+        {
+            parameters[index] = keyParameters[index].getValue();
+        }
+        return new GeneratedCacheKeyImpl(parameters);
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CacheKeyInvocationContextImpl.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CacheKeyInvocationContextImpl.java
new file mode 100644
index 0000000..a0c0a52
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CacheKeyInvocationContextImpl.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.cdi;
+
+import java.lang.annotation.Annotation;
+
+import javax.cache.annotation.CacheInvocationParameter;
+import javax.cache.annotation.CacheKeyInvocationContext;
+import javax.interceptor.InvocationContext;
+
+public class CacheKeyInvocationContextImpl<A extends Annotation> extends CacheInvocationContextImpl<A> implements CacheKeyInvocationContext<A>
+{
+    private CacheInvocationParameter[] keyParams = null;
+    private CacheInvocationParameter valueParam = null;
+
+    public CacheKeyInvocationContextImpl(final InvocationContext delegate, final A annotation, final String name,
+                                         final CDIJCacheHelper.MethodMeta methodMeta)
+    {
+        super(delegate, annotation, name, methodMeta);
+    }
+
+    @Override
+    public CacheInvocationParameter[] getKeyParameters()
+    {
+        if (keyParams == null)
+        {
+            keyParams = doGetAllParameters(meta.getKeysIndices());
+        }
+        return keyParams;
+    }
+
+    @Override
+    public CacheInvocationParameter getValueParameter()
+    {
+        if (valueParam == null)
+        {
+            valueParam = meta.getValueIndex() >= 0 ? doGetAllParameters(new Integer[]{meta.getValueIndex()})[0] : null;
+        }
+        return valueParam;
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CacheMethodDetailsImpl.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CacheMethodDetailsImpl.java
new file mode 100644
index 0000000..b9a0df1
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CacheMethodDetailsImpl.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.cdi;
+
+import javax.cache.annotation.CacheMethodDetails;
+import javax.interceptor.InvocationContext;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.Set;
+
+public class CacheMethodDetailsImpl<A extends Annotation> implements CacheMethodDetails<A>
+{
+    protected final InvocationContext delegate;
+    private final Set<Annotation> annotations;
+    private final A cacheAnnotation;
+    private final String cacheName;
+    protected final CDIJCacheHelper.MethodMeta meta;
+
+    public CacheMethodDetailsImpl(final InvocationContext delegate, final A cacheAnnotation, final String cacheName,
+                                  final CDIJCacheHelper.MethodMeta meta)
+    {
+        this.delegate = delegate;
+        this.annotations = meta.getAnnotations();
+        this.cacheAnnotation = cacheAnnotation;
+        this.cacheName = cacheName;
+        this.meta = meta;
+    }
+
+    @Override
+    public Method getMethod()
+    {
+        return delegate.getMethod();
+    }
+
+    @Override
+    public Set<Annotation> getAnnotations()
+    {
+        return annotations;
+    }
+
+    @Override
+    public A getCacheAnnotation()
+    {
+        return cacheAnnotation;
+    }
+
+    @Override
+    public String getCacheName()
+    {
+        return cacheName;
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CachePutInterceptor.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CachePutInterceptor.java
new file mode 100644
index 0000000..ec8416d
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CachePutInterceptor.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.cdi;
+
+import java.io.Serializable;
+
+import javax.annotation.Priority;
+import javax.cache.Cache;
+import javax.cache.annotation.CacheKeyInvocationContext;
+import javax.cache.annotation.CachePut;
+import javax.cache.annotation.CacheResolver;
+import javax.cache.annotation.CacheResolverFactory;
+import javax.cache.annotation.GeneratedCacheKey;
+import javax.inject.Inject;
+import javax.interceptor.AroundInvoke;
+import javax.interceptor.Interceptor;
+import javax.interceptor.InvocationContext;
+
+@CachePut
+@Interceptor
+@Priority(/*LIBRARY_BEFORE*/1000)
+public class CachePutInterceptor implements Serializable
+{
+    @Inject
+    private CDIJCacheHelper helper;
+
+    @AroundInvoke
+    public Object cache(final InvocationContext ic) throws Throwable
+    {
+        final CDIJCacheHelper.MethodMeta methodMeta = helper.findMeta(ic);
+
+        final String cacheName = methodMeta.getCachePutCacheName();
+
+        final CacheResolverFactory cacheResolverFactory = methodMeta.getCachePutResolverFactory();
+        final CacheKeyInvocationContext<CachePut> context = new CacheKeyInvocationContextImpl<>(
+                ic, methodMeta.getCachePut(), cacheName, methodMeta);
+        final CacheResolver cacheResolver = cacheResolverFactory.getCacheResolver(context);
+        final Cache<Object, Object> cache = cacheResolver.resolveCache(context);
+
+        final GeneratedCacheKey cacheKey = methodMeta.getCachePutKeyGenerator().generateCacheKey(context);
+        final CachePut cachePut = methodMeta.getCachePut();
+        final boolean afterInvocation = methodMeta.isCachePutAfter();
+
+        if (!afterInvocation)
+        {
+            cache.put(cacheKey, context.getValueParameter());
+        }
+
+        final Object result;
+        try
+        {
+            result = ic.proceed();
+        }
+        catch (final Throwable t)
+        {
+            if (afterInvocation)
+            {
+                if (helper.isIncluded(t.getClass(), cachePut.cacheFor(), cachePut.noCacheFor()))
+                {
+                    cache.put(cacheKey, context.getValueParameter());
+                }
+            }
+
+            throw t;
+        }
+
+        if (afterInvocation)
+        {
+            cache.put(cacheKey, context.getValueParameter());
+        }
+
+        return result;
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CacheRemoveAllInterceptor.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CacheRemoveAllInterceptor.java
new file mode 100644
index 0000000..b719852
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CacheRemoveAllInterceptor.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.cdi;
+
+import java.io.Serializable;
+
+import javax.annotation.Priority;
+import javax.cache.Cache;
+import javax.cache.annotation.CacheKeyInvocationContext;
+import javax.cache.annotation.CacheRemoveAll;
+import javax.cache.annotation.CacheResolver;
+import javax.cache.annotation.CacheResolverFactory;
+import javax.inject.Inject;
+import javax.interceptor.AroundInvoke;
+import javax.interceptor.Interceptor;
+import javax.interceptor.InvocationContext;
+
+@CacheRemoveAll
+@Interceptor
+@Priority(/*LIBRARY_BEFORE*/1000)
+public class CacheRemoveAllInterceptor implements Serializable
+{
+    @Inject
+    private CDIJCacheHelper helper;
+
+    @AroundInvoke
+    public Object cache(final InvocationContext ic) throws Throwable
+    {
+        final CDIJCacheHelper.MethodMeta methodMeta = helper.findMeta(ic);
+
+        final String cacheName = methodMeta.getCacheRemoveAllCacheName();
+
+        final CacheResolverFactory cacheResolverFactory = methodMeta.getCacheRemoveAllResolverFactory();
+        final CacheKeyInvocationContext<CacheRemoveAll> context = new CacheKeyInvocationContextImpl<>(
+                ic, methodMeta.getCacheRemoveAll(), cacheName, methodMeta);
+        final CacheResolver cacheResolver = cacheResolverFactory.getCacheResolver(context);
+        final Cache<Object, Object> cache = cacheResolver.resolveCache(context);
+
+        final boolean afterInvocation = methodMeta.isCachePutAfter();
+        if (!afterInvocation)
+        {
+            cache.removeAll();
+        }
+
+        final Object result;
+        try
+        {
+            result = ic.proceed();
+        }
+        catch (final Throwable t)
+        {
+            if (afterInvocation)
+            {
+                if (helper.isIncluded(t.getClass(), methodMeta.getCacheRemoveAll().evictFor(), methodMeta.getCacheRemoveAll().noEvictFor()))
+                {
+                    cache.removeAll();
+                }
+            }
+            throw t;
+        }
+
+        if (afterInvocation)
+        {
+            cache.removeAll();
+        }
+
+        return result;
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CacheRemoveInterceptor.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CacheRemoveInterceptor.java
new file mode 100644
index 0000000..cfd1738
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CacheRemoveInterceptor.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.cdi;
+
+import java.io.Serializable;
+
+import javax.annotation.Priority;
+import javax.cache.Cache;
+import javax.cache.annotation.CacheKeyInvocationContext;
+import javax.cache.annotation.CacheRemove;
+import javax.cache.annotation.CacheResolver;
+import javax.cache.annotation.CacheResolverFactory;
+import javax.cache.annotation.GeneratedCacheKey;
+import javax.inject.Inject;
+import javax.interceptor.AroundInvoke;
+import javax.interceptor.Interceptor;
+import javax.interceptor.InvocationContext;
+
+@CacheRemove
+@Interceptor
+@Priority(/*LIBRARY_BEFORE*/1000)
+public class CacheRemoveInterceptor implements Serializable
+{
+    @Inject
+    private CDIJCacheHelper helper;
+
+    @AroundInvoke
+    public Object cache(final InvocationContext ic) throws Throwable
+    {
+        final CDIJCacheHelper.MethodMeta methodMeta = helper.findMeta(ic);
+
+        final String cacheName = methodMeta.getCacheRemoveCacheName();
+
+        final CacheResolverFactory cacheResolverFactory = methodMeta.getCacheRemoveResolverFactory();
+        final CacheKeyInvocationContext<CacheRemove> context = new CacheKeyInvocationContextImpl<>(
+                ic, methodMeta.getCacheRemove(), cacheName, methodMeta);
+        final CacheResolver cacheResolver = cacheResolverFactory.getCacheResolver(context);
+        final Cache<Object, Object> cache = cacheResolver.resolveCache(context);
+
+        final GeneratedCacheKey cacheKey = methodMeta.getCacheRemoveKeyGenerator().generateCacheKey(context);
+        final CacheRemove cacheRemove = methodMeta.getCacheRemove();
+        final boolean afterInvocation = methodMeta.isCacheRemoveAfter();
+
+        if (!afterInvocation)
+        {
+            cache.remove(cacheKey);
+        }
+
+        final Object result;
+        try
+        {
+            result = ic.proceed();
+        }
+        catch (final Throwable t)
+        {
+            if (afterInvocation)
+            {
+                if (helper.isIncluded(t.getClass(), cacheRemove.evictFor(), cacheRemove.noEvictFor()))
+                {
+                    cache.remove(cacheKey);
+                }
+            }
+
+            throw t;
+        }
+
+        if (afterInvocation)
+        {
+            cache.remove(cacheKey);
+        }
+
+        return result;
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CacheResolverFactoryImpl.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CacheResolverFactoryImpl.java
new file mode 100644
index 0000000..7adae36
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CacheResolverFactoryImpl.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.cdi;
+
+import javax.cache.Cache;
+import javax.cache.CacheManager;
+import javax.cache.Caching;
+import javax.cache.annotation.CacheMethodDetails;
+import javax.cache.annotation.CacheResolver;
+import javax.cache.annotation.CacheResolverFactory;
+import javax.cache.annotation.CacheResult;
+import javax.cache.configuration.MutableConfiguration;
+import javax.cache.spi.CachingProvider;
+import java.lang.annotation.Annotation;
+
+public class CacheResolverFactoryImpl implements CacheResolverFactory
+{
+    private final CacheManager cacheManager;
+    private final CachingProvider provider;
+
+    public CacheResolverFactoryImpl()
+    {
+        provider = Caching.getCachingProvider();
+        cacheManager = provider.getCacheManager(provider.getDefaultURI(), provider.getDefaultClassLoader());
+    }
+
+    @Override
+    public CacheResolver getCacheResolver(CacheMethodDetails<? extends Annotation> cacheMethodDetails)
+    {
+        return findCacheResolver(cacheMethodDetails.getCacheName());
+    }
+
+    @Override
+    public CacheResolver getExceptionCacheResolver(final CacheMethodDetails<CacheResult> cacheMethodDetails)
+    {
+        final String exceptionCacheName = cacheMethodDetails.getCacheAnnotation().exceptionCacheName();
+        if (exceptionCacheName == null || exceptionCacheName.isEmpty())
+        {
+            throw new IllegalArgumentException("CacheResult.exceptionCacheName() not specified");
+        }
+        return findCacheResolver(exceptionCacheName);
+    }
+
+    private CacheResolver findCacheResolver(String exceptionCacheName)
+    {
+        Cache<?, ?> cache = cacheManager.getCache(exceptionCacheName);
+        if (cache == null)
+        {
+            cache = createCache(exceptionCacheName);
+        }
+        return new CacheResolverImpl(cache);
+    }
+
+    private Cache<?, ?> createCache(final String exceptionCacheName)
+    {
+        cacheManager.createCache(exceptionCacheName, new MutableConfiguration<>().setStoreByValue(false));
+        return cacheManager.getCache(exceptionCacheName);
+    }
+
+    public void release()
+    {
+        cacheManager.close();
+        provider.close();
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CacheResolverImpl.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CacheResolverImpl.java
new file mode 100644
index 0000000..49e1e91
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CacheResolverImpl.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.cdi;
+
+import javax.cache.Cache;
+import javax.cache.annotation.CacheInvocationContext;
+import javax.cache.annotation.CacheResolver;
+import java.lang.annotation.Annotation;
+
+public class CacheResolverImpl implements CacheResolver
+{
+    private final Cache<?, ?> delegate;
+
+    public CacheResolverImpl(final Cache<?, ?> cache)
+    {
+        delegate = cache;
+    }
+
+    @Override
+    public <K, V> Cache<K, V> resolveCache(final CacheInvocationContext<? extends Annotation> cacheInvocationContext)
+    {
+        return (Cache<K, V>) delegate;
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CacheResultInterceptor.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CacheResultInterceptor.java
new file mode 100644
index 0000000..71701c3
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/CacheResultInterceptor.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.cdi;
+
+import java.io.Serializable;
+import javax.annotation.Priority;
+import javax.cache.Cache;
+import javax.cache.annotation.CacheKeyInvocationContext;
+import javax.cache.annotation.CacheResolver;
+import javax.cache.annotation.CacheResolverFactory;
+import javax.cache.annotation.CacheResult;
+import javax.cache.annotation.GeneratedCacheKey;
+import javax.inject.Inject;
+import javax.interceptor.AroundInvoke;
+import javax.interceptor.Interceptor;
+import javax.interceptor.InvocationContext;
+
+@CacheResult
+@Interceptor
+@Priority(/*LIBRARY_BEFORE*/1000)
+public class CacheResultInterceptor implements Serializable
+{
+    @Inject
+    private CDIJCacheHelper helper;
+
+    @AroundInvoke
+    public Object cache(final InvocationContext ic) throws Throwable
+    {
+        final CDIJCacheHelper.MethodMeta methodMeta = helper.findMeta(ic);
+
+        final String cacheName = methodMeta.getCacheResultCacheName();
+
+        final CacheResult cacheResult = methodMeta.getCacheResult();
+        final CacheKeyInvocationContext<CacheResult> context = new CacheKeyInvocationContextImpl<>(
+                ic, cacheResult, cacheName, methodMeta);
+
+        final CacheResolverFactory cacheResolverFactory = methodMeta.getCacheResultResolverFactory();
+        final CacheResolver cacheResolver = cacheResolverFactory.getCacheResolver(context);
+        final Cache<Object, Object> cache = cacheResolver.resolveCache(context);
+
+        final GeneratedCacheKey cacheKey = methodMeta.getCacheResultKeyGenerator().generateCacheKey(context);
+
+        Cache<Object, Object> exceptionCache = null; // lazily created
+
+        Object result;
+        if (!cacheResult.skipGet())
+        {
+            result = cache.get(cacheKey);
+            if (result != null)
+            {
+                return result;
+            }
+
+
+            if (!cacheResult.exceptionCacheName().isEmpty())
+            {
+                exceptionCache = cacheResolverFactory.getExceptionCacheResolver(context).resolveCache(context);
+                final Object exception = exceptionCache.get(cacheKey);
+                if (exception != null)
+                {
+                    throw Throwable.class.cast(exception);
+                }
+            }
+        }
+
+        try
+        {
+            result = ic.proceed();
+            if (result != null)
+            {
+                cache.put(cacheKey, result);
+            }
+
+            return result;
+        }
+        catch (final Throwable t)
+        {
+            if (helper.isIncluded(t.getClass(), cacheResult.cachedExceptions(), cacheResult.nonCachedExceptions()))
+            {
+                if (exceptionCache == null)
+                {
+                    exceptionCache = cacheResolverFactory.getExceptionCacheResolver(context).resolveCache(context);
+                }
+                exceptionCache.put(cacheKey, t);
+            }
+            throw t;
+        }
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/GeneratedCacheKeyImpl.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/GeneratedCacheKeyImpl.java
new file mode 100644
index 0000000..1802fc0
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/GeneratedCacheKeyImpl.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.cdi;
+
+import javax.cache.annotation.GeneratedCacheKey;
+import java.util.Arrays;
+
+public class GeneratedCacheKeyImpl implements GeneratedCacheKey
+{
+    private final Object[] params;
+    private final int hash;
+
+    public GeneratedCacheKeyImpl(final Object[] parameters)
+    {
+        this.params = parameters;
+        this.hash = Arrays.deepHashCode(parameters);
+    }
+
+    @Override
+    public boolean equals(final Object o)
+    {
+        if (this == o)
+        {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass())
+        {
+            return false;
+        }
+        final GeneratedCacheKeyImpl that = GeneratedCacheKeyImpl.class.cast(o);
+        return Arrays.deepEquals(params, that.params);
+
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return hash;
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/MakeJCacheCDIInterceptorFriendly.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/MakeJCacheCDIInterceptorFriendly.java
new file mode 100644
index 0000000..22232d0
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/cdi/MakeJCacheCDIInterceptorFriendly.java
@@ -0,0 +1,212 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.cdi;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import javax.cache.annotation.CachePut;
+import javax.cache.annotation.CacheRemove;
+import javax.cache.annotation.CacheRemoveAll;
+import javax.cache.annotation.CacheResult;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.Any;
+import javax.enterprise.inject.Default;
+import javax.enterprise.inject.spi.AfterBeanDiscovery;
+import javax.enterprise.inject.spi.AnnotatedType;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.BeforeBeanDiscovery;
+import javax.enterprise.inject.spi.Extension;
+import javax.enterprise.inject.spi.InjectionPoint;
+import javax.enterprise.inject.spi.InjectionTarget;
+import javax.enterprise.inject.spi.PassivationCapable;
+import javax.enterprise.inject.spi.ProcessAnnotatedType;
+import javax.enterprise.util.AnnotationLiteral;
+
+import static java.util.Arrays.asList;
+
+// TODO: observe annotated type (or maybe sthg else) to cache data and inject this extension (used as metadata cache)
+// to get class model and this way allow to add cache annotation on the fly - == avoid java pure reflection to get metadata
+public class MakeJCacheCDIInterceptorFriendly implements Extension
+{
+    private static final AtomicInteger id = new AtomicInteger();
+    private static final boolean USE_ID = !Boolean.getBoolean("org.apache.commons.jcs3.cdi.skip-id");
+
+    private boolean needHelper = true;
+
+    protected void discoverInterceptorBindings(final @Observes BeforeBeanDiscovery beforeBeanDiscoveryEvent,
+                                               final BeanManager bm)
+    {
+        // CDI 1.1 will just pick createAnnotatedType(X) as beans so we'll skip our HelperBean
+        // but CDI 1.0 needs our HelperBean + interceptors in beans.xml like:
+        /*
+        <beans xmlns="http://java.sun.com/xml/ns/javaee"
+               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+               xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
+              http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
+          <interceptors>
+            <class>org.apache.commons.jcs3.jcache.cdi.CacheResultInterceptor</class>
+            <class>org.apache.commons.jcs3.jcache.cdi.CacheRemoveAllInterceptor</class>
+            <class>org.apache.commons.jcs3.jcache.cdi.CacheRemoveInterceptor</class>
+            <class>org.apache.commons.jcs3.jcache.cdi.CachePutInterceptor</class>
+          </interceptors>
+        </beans>
+         */
+        bm.createAnnotatedType(CDIJCacheHelper.class);
+        for (final Class<?> interceptor : asList(
+                CachePutInterceptor.class, CacheRemoveInterceptor.class,
+                CacheRemoveAllInterceptor.class, CacheResultInterceptor.class)) {
+            beforeBeanDiscoveryEvent.addAnnotatedType(bm.createAnnotatedType(interceptor));
+        }
+        for (final Class<? extends Annotation> interceptor : asList(
+                CachePut.class, CacheRemove.class,
+                CacheRemoveAll.class, CacheResult.class)) {
+            beforeBeanDiscoveryEvent.addInterceptorBinding(interceptor);
+        }
+    }
+
+    protected void addHelper(final @Observes AfterBeanDiscovery afterBeanDiscovery,
+                             final BeanManager bm)
+    {
+        if (!needHelper) {
+            return;
+        }
+        /* CDI >= 1.1 only. Actually we shouldn't go here with CDI 1.1 since we defined the annotated type for the helper
+        final AnnotatedType<CDIJCacheHelper> annotatedType = bm.createAnnotatedType(CDIJCacheHelper.class);
+        final BeanAttributes<CDIJCacheHelper> beanAttributes = bm.createBeanAttributes(annotatedType);
+        final InjectionTarget<CDIJCacheHelper> injectionTarget = bm.createInjectionTarget(annotatedType);
+        final Bean<CDIJCacheHelper> bean = bm.createBean(beanAttributes, CDIJCacheHelper.class, new InjectionTargetFactory<CDIJCacheHelper>() {
+            @Override
+            public InjectionTarget<CDIJCacheHelper> createInjectionTarget(Bean<CDIJCacheHelper> bean) {
+                return injectionTarget;
+            }
+        });
+        */
+        final AnnotatedType<CDIJCacheHelper> annotatedType = bm.createAnnotatedType(CDIJCacheHelper.class);
+        final InjectionTarget<CDIJCacheHelper> injectionTarget = bm.createInjectionTarget(annotatedType);
+        final HelperBean bean = new HelperBean(annotatedType, injectionTarget, findIdSuffix());
+        afterBeanDiscovery.addBean(bean);
+    }
+
+    protected void vetoScannedCDIJCacheHelperQualifiers(final @Observes ProcessAnnotatedType<CDIJCacheHelper> pat) {
+        if (!needHelper) { // already seen, shouldn't really happen,just a protection
+            pat.veto();
+        }
+        needHelper = false;
+    }
+
+    // TODO: make it better for ear+cluster case with CDI 1.0
+    private String findIdSuffix() {
+        // big disadvantage is all deployments of a cluster needs to be in the exact same order but it works with ears
+        if (USE_ID) {
+            return "lib" + id.incrementAndGet();
+        }
+        return "default";
+    }
+
+    public static class HelperBean implements Bean<CDIJCacheHelper>, PassivationCapable {
+        private final AnnotatedType<CDIJCacheHelper> at;
+        private final InjectionTarget<CDIJCacheHelper> it;
+        private final HashSet<Annotation> qualifiers;
+        private final String id;
+
+        public HelperBean(final AnnotatedType<CDIJCacheHelper> annotatedType,
+                          final InjectionTarget<CDIJCacheHelper> injectionTarget,
+                          final String id) {
+            this.at = annotatedType;
+            this.it = injectionTarget;
+            this.id =  "JCS#CDIHelper#" + id;
+
+            this.qualifiers = new HashSet<>();
+            this.qualifiers.add(new AnnotationLiteral<Default>() {});
+            this.qualifiers.add(new AnnotationLiteral<Any>() {});
+        }
+
+        @Override
+        public Set<InjectionPoint> getInjectionPoints() {
+            return it.getInjectionPoints();
+        }
+
+        @Override
+        public Class<?> getBeanClass() {
+            return at.getJavaClass();
+        }
+
+        @Override
+        public boolean isNullable() {
+            return false;
+        }
+
+        @Override
+        public Set<Type> getTypes() {
+            return at.getTypeClosure();
+        }
+
+        @Override
+        public Set<Annotation> getQualifiers() {
+            return qualifiers;
+        }
+
+        @Override
+        public Class<? extends Annotation> getScope() {
+            return ApplicationScoped.class;
+        }
+
+        @Override
+        public String getName() {
+            return null;
+        }
+
+        @Override
+        public Set<Class<? extends Annotation>> getStereotypes() {
+            return Collections.emptySet();
+        }
+
+        @Override
+        public boolean isAlternative() {
+            return false;
+        }
+
+        @Override
+        public CDIJCacheHelper create(final CreationalContext<CDIJCacheHelper> context) {
+            final CDIJCacheHelper produce = it.produce(context);
+            it.inject(produce, context);
+            it.postConstruct(produce);
+            return produce;
+        }
+
+        @Override
+        public void destroy(final CDIJCacheHelper instance, final CreationalContext<CDIJCacheHelper> context) {
+            it.preDestroy(instance);
+            it.dispose(instance);
+            context.release();
+        }
+
+        @Override
+        public String getId() {
+            return id;
+        }
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/jmx/ConfigurableMBeanServerIdBuilder.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/jmx/ConfigurableMBeanServerIdBuilder.java
new file mode 100644
index 0000000..3b4acde
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/jmx/ConfigurableMBeanServerIdBuilder.java
@@ -0,0 +1,170 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.jmx;
+
+import javax.management.ListenerNotFoundException;
+import javax.management.MBeanNotificationInfo;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerBuilder;
+import javax.management.MBeanServerDelegate;
+import javax.management.Notification;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+public class ConfigurableMBeanServerIdBuilder extends MBeanServerBuilder
+{
+    private static ConcurrentMap<Key, MBeanServer> JVM_SINGLETONS = new ConcurrentHashMap<>();
+
+    private static class Key
+    {
+        private final String domain;
+        private final MBeanServer outer;
+
+        private Key(final String domain, final MBeanServer outer)
+        {
+            this.domain = domain;
+            this.outer = outer;
+        }
+
+        @Override
+        public boolean equals(final Object o)
+        {
+            if (this == o)
+                return true;
+            if (o == null || getClass() != o.getClass())
+                return false;
+
+            final Key key = Key.class.cast(o);
+            return !(domain != null ? !domain.equals(key.domain) : key.domain != null)
+                    && !(outer != null ? !outer.equals(key.outer) : key.outer != null);
+
+        }
+
+        @Override
+        public int hashCode()
+        {
+            int result = domain != null ? domain.hashCode() : 0;
+            result = 31 * result + (outer != null ? outer.hashCode() : 0);
+            return result;
+        }
+    }
+
+    @Override
+    public MBeanServer newMBeanServer(final String defaultDomain, final MBeanServer outer, final MBeanServerDelegate delegate)
+    {
+        final Key key = new Key(defaultDomain, outer);
+        MBeanServer server = JVM_SINGLETONS.get(key);
+        if (server == null)
+        {
+            server = super.newMBeanServer(defaultDomain, outer, new ForceIdMBeanServerDelegate(delegate));
+            final MBeanServer existing = JVM_SINGLETONS.putIfAbsent(key, server);
+            if (existing != null)
+            {
+                server = existing;
+            }
+        }
+        return server;
+    }
+
+    private class ForceIdMBeanServerDelegate extends MBeanServerDelegate
+    {
+        private final MBeanServerDelegate delegate;
+
+        public ForceIdMBeanServerDelegate(final MBeanServerDelegate delegate)
+        {
+            this.delegate = delegate;
+        }
+
+        @Override
+        public String getMBeanServerId()
+        {
+            return System.getProperty("org.jsr107.tck.management.agentId", delegate.getMBeanServerId());
+        }
+
+        @Override
+        public String getSpecificationName()
+        {
+            return delegate.getSpecificationName();
+        }
+
+        @Override
+        public String getSpecificationVersion()
+        {
+            return delegate.getSpecificationVersion();
+        }
+
+        @Override
+        public String getSpecificationVendor()
+        {
+            return delegate.getSpecificationVendor();
+        }
+
+        @Override
+        public String getImplementationName()
+        {
+            return delegate.getImplementationName();
+        }
+
+        @Override
+        public String getImplementationVersion()
+        {
+            return delegate.getImplementationVersion();
+        }
+
+        @Override
+        public String getImplementationVendor()
+        {
+            return delegate.getImplementationVendor();
+        }
+
+        @Override
+        public MBeanNotificationInfo[] getNotificationInfo()
+        {
+            return delegate.getNotificationInfo();
+        }
+
+        @Override
+        public void addNotificationListener(final NotificationListener listener, final NotificationFilter filter, final Object handback)
+                throws IllegalArgumentException
+        {
+            delegate.addNotificationListener(listener, filter, handback);
+        }
+
+        @Override
+        public void removeNotificationListener(final NotificationListener listener, final NotificationFilter filter, final Object handback)
+                throws ListenerNotFoundException
+        {
+            delegate.removeNotificationListener(listener, filter, handback);
+        }
+
+        @Override
+        public void removeNotificationListener(final NotificationListener listener) throws ListenerNotFoundException
+        {
+            delegate.removeNotificationListener(listener);
+        }
+
+        @Override
+        public void sendNotification(final Notification notification)
+        {
+            delegate.sendNotification(notification);
+        }
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/jmx/JCSCacheMXBean.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/jmx/JCSCacheMXBean.java
new file mode 100644
index 0000000..d397e77
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/jmx/JCSCacheMXBean.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.jmx;
+
+import javax.cache.Cache;
+import javax.cache.configuration.CompleteConfiguration;
+import javax.cache.configuration.Configuration;
+import javax.cache.management.CacheMXBean;
+
+public class JCSCacheMXBean<K, V> implements CacheMXBean
+{
+    private final Cache<K, V> delegate;
+
+    public JCSCacheMXBean(final Cache<K, V> delegate)
+    {
+        this.delegate = delegate;
+    }
+
+    private Configuration<K, V> config()
+    {
+        return delegate.getConfiguration(Configuration.class);
+    }
+
+    private CompleteConfiguration<K, V> completeConfig()
+    {
+        return delegate.getConfiguration(CompleteConfiguration.class);
+    }
+
+    @Override
+    public String getKeyType()
+    {
+        return config().getKeyType().getName();
+    }
+
+    @Override
+    public String getValueType()
+    {
+        return config().getValueType().getName();
+    }
+
+    @Override
+    public boolean isReadThrough()
+    {
+        try
+        {
+            return completeConfig().isReadThrough();
+        }
+        catch (final Exception e)
+        {
+            return false;
+        }
+    }
+
+    @Override
+    public boolean isWriteThrough()
+    {
+        try
+        {
+            return completeConfig().isWriteThrough();
+        }
+        catch (final Exception e)
+        {
+            return false;
+        }
+    }
+
+    @Override
+    public boolean isStoreByValue()
+    {
+        return config().isStoreByValue();
+    }
+
+    @Override
+    public boolean isStatisticsEnabled()
+    {
+        try
+        {
+            return completeConfig().isStatisticsEnabled();
+        }
+        catch (final Exception e)
+        {
+            return false;
+        }
+    }
+
+    @Override
+    public boolean isManagementEnabled()
+    {
+        try
+        {
+            return completeConfig().isManagementEnabled();
+        }
+        catch (final Exception e)
+        {
+            return false;
+        }
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/jmx/JCSCacheStatisticsMXBean.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/jmx/JCSCacheStatisticsMXBean.java
new file mode 100644
index 0000000..37db76e
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/jmx/JCSCacheStatisticsMXBean.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.jmx;
+
+import javax.cache.management.CacheStatisticsMXBean;
+
+import org.apache.commons.jcs3.jcache.Statistics;
+
+public class JCSCacheStatisticsMXBean implements CacheStatisticsMXBean
+{
+    private final Statistics statistics;
+
+    public JCSCacheStatisticsMXBean(final Statistics stats)
+    {
+        this.statistics = stats;
+    }
+
+    @Override
+    public void clear()
+    {
+        statistics.reset();
+    }
+
+    @Override
+    public long getCacheHits()
+    {
+        return statistics.getHits();
+    }
+
+    @Override
+    public float getCacheHitPercentage()
+    {
+        final long hits = getCacheHits();
+        if (hits == 0)
+        {
+            return 0;
+        }
+        return (float) hits / getCacheGets() * 100.0f;
+    }
+
+    @Override
+    public long getCacheMisses()
+    {
+        return statistics.getMisses();
+    }
+
+    @Override
+    public float getCacheMissPercentage()
+    {
+        final long misses = getCacheMisses();
+        if (misses == 0)
+        {
+            return 0;
+        }
+        return (float) misses / getCacheGets() * 100.0f;
+    }
+
+    @Override
+    public long getCacheGets()
+    {
+        return getCacheHits() + getCacheMisses();
+    }
+
+    @Override
+    public long getCachePuts()
+    {
+        return statistics.getPuts();
+    }
+
+    @Override
+    public long getCacheRemovals()
+    {
+        return statistics.getRemovals();
+    }
+
+    @Override
+    public long getCacheEvictions()
+    {
+        return statistics.getEvictions();
+    }
+
+    @Override
+    public float getAverageGetTime()
+    {
+        return averageTime(statistics.getTimeTakenForGets());
+    }
+
+    @Override
+    public float getAveragePutTime()
+    {
+        return averageTime(statistics.getTimeTakenForPuts());
+    }
+
+    @Override
+    public float getAverageRemoveTime()
+    {
+        return averageTime(statistics.getTimeTakenForRemovals());
+    }
+
+    private float averageTime(final long timeTaken)
+    {
+        final long gets = getCacheGets();
+        if (timeTaken == 0 || gets == 0)
+        {
+            return 0;
+        }
+        return timeTaken / gets;
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/jmx/JMXs.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/jmx/JMXs.java
new file mode 100644
index 0000000..31d1f60
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/jmx/JMXs.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.jmx;
+
+import javax.management.MBeanServer;
+import javax.management.MBeanServerFactory;
+import javax.management.ObjectName;
+import java.lang.management.ManagementFactory;
+
+public class JMXs
+{
+    private static final MBeanServer SERVER = findMBeanServer();
+
+    public static MBeanServer server()
+    {
+        return SERVER;
+    }
+
+    public static void register(final ObjectName on, final Object bean)
+    {
+        if (!SERVER.isRegistered(on))
+        {
+            try
+            {
+                SERVER.registerMBean(bean, on);
+            }
+            catch (final Exception e)
+            {
+                throw new IllegalStateException(e.getMessage(), e);
+            }
+        }
+    }
+
+    public static void unregister(final ObjectName on)
+    {
+        if (SERVER.isRegistered(on))
+        {
+            try
+            {
+                SERVER.unregisterMBean(on);
+            }
+            catch (final Exception e)
+            {
+                // no-op
+            }
+        }
+    }
+
+    private static MBeanServer findMBeanServer()
+    {
+        if (System.getProperty("javax.management.builder.initial") != null)
+        {
+            return MBeanServerFactory.createMBeanServer();
+        }
+        return ManagementFactory.getPlatformMBeanServer();
+    }
+
+    private JMXs()
+    {
+        // no-op
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/lang/DefaultSubsitutor.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/lang/DefaultSubsitutor.java
new file mode 100644
index 0000000..0fa9cad
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/lang/DefaultSubsitutor.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.commons.jcs3.jcache.lang;
+
+public class DefaultSubsitutor implements Subsitutor
+{
+    @Override
+    public String substitute(final String value)
+    {
+        if (value.startsWith("${") && value.endsWith("}")) {
+            return System.getProperty(value.substring("${".length(), value.length() - 1), value);
+        }
+        return value;
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/lang/Lang3Substitutor.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/lang/Lang3Substitutor.java
new file mode 100644
index 0000000..88be6b4
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/lang/Lang3Substitutor.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.commons.jcs3.jcache.lang;
+
+import org.apache.commons.lang3.text.StrSubstitutor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class Lang3Substitutor implements Subsitutor
+{
+    private static final StrSubstitutor SUBSTITUTOR = new StrSubstitutor(new HashMap<String, Object>() {{
+        putAll(Map.class.cast(System.getProperties()));
+        putAll(System.getenv());
+    }});
+
+    @Override
+    public String substitute(final String value)
+    {
+        return SUBSTITUTOR.replace(value);
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/lang/Subsitutor.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/lang/Subsitutor.java
new file mode 100644
index 0000000..3e07545
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/lang/Subsitutor.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.commons.jcs3.jcache.lang;
+
+public interface Subsitutor
+{
+    String substitute(String value);
+
+    public static class Helper {
+        public static final Subsitutor INSTANCE;
+        static {
+            Subsitutor value = null;
+            for (final String name : new String[]
+            { // ordered by features
+                    "org.apache.commons.jcs3.jcache.lang.Lang3Substitutor",
+                    "org.apache.commons.jcs3.jcache.lang.DefaultSubsitutor"
+            })
+            {
+                try
+                {
+                    value = Subsitutor.class.cast(
+                            Subsitutor.class.getClassLoader().loadClass(name).newInstance());
+                    value.substitute("${java.version}"); // ensure it works
+                }
+                catch (final Throwable e) // not Exception otherwise NoClassDefFoundError
+                {
+                    // no-op: next
+                }
+            }
+            if (value == null) {
+                throw new IllegalStateException("Can't find a " + Subsitutor.class.getName());
+            }
+            INSTANCE = value;
+        }
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/proxy/ClassLoaderAwareCache.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/proxy/ClassLoaderAwareCache.java
new file mode 100644
index 0000000..1522c4d
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/proxy/ClassLoaderAwareCache.java
@@ -0,0 +1,514 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.proxy;
+
+import javax.cache.Cache;
+import javax.cache.CacheManager;
+import javax.cache.configuration.CacheEntryListenerConfiguration;
+import javax.cache.configuration.Configuration;
+import javax.cache.integration.CompletionListener;
+import javax.cache.processor.EntryProcessor;
+import javax.cache.processor.EntryProcessorException;
+import javax.cache.processor.EntryProcessorResult;
+
+import org.apache.commons.jcs3.jcache.JCSCache;
+
+import java.io.Serializable;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+// don't use a proxy, reflection is too slow here :(
+public class ClassLoaderAwareCache<K, V> implements Cache<K, V>
+{
+    private final ClassLoader loader;
+    private final JCSCache<K, V> delegate;
+
+    public ClassLoaderAwareCache(final ClassLoader loader, final JCSCache<K, V> delegate)
+    {
+        this.loader = loader;
+        this.delegate = delegate;
+    }
+
+    private ClassLoader before(final Thread thread)
+    {
+        final ClassLoader tccl = thread.getContextClassLoader();
+        thread.setContextClassLoader(loader);
+        return tccl;
+    }
+
+    @Override
+    public V get(final K key)
+    {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try
+        {
+            return delegate.get(key);
+        }
+        finally
+        {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public Map<K, V> getAll(final Set<? extends K> keys)
+    {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try
+        {
+            return delegate.getAll(keys);
+        }
+        finally
+        {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public boolean containsKey(final K key)
+    {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try
+        {
+            return delegate.containsKey(key);
+        }
+        finally
+        {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public void loadAll(final Set<? extends K> keys, boolean replaceExistingValues, final CompletionListener completionListener)
+    {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try
+        {
+            delegate.loadAll(keys, replaceExistingValues, completionListener);
+        }
+        finally
+        {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public void put(final K key, final V value)
+    {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try
+        {
+            delegate.put(key, value);
+        }
+        finally
+        {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public V getAndPut(final K key, final V value)
+    {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try
+        {
+            return delegate.getAndPut(key, value);
+        }
+        finally
+        {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public void putAll(final Map<? extends K, ? extends V> map)
+    {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try
+        {
+            delegate.putAll(map);
+        }
+        finally
+        {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public boolean putIfAbsent(final K key, final V value)
+    {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try
+        {
+            return delegate.putIfAbsent(key, value);
+        }
+        finally
+        {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public boolean remove(final K key)
+    {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try
+        {
+            return delegate.remove(key);
+        }
+        finally
+        {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public boolean remove(final K key, final V oldValue)
+    {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try
+        {
+            return delegate.remove(key, oldValue);
+        }
+        finally
+        {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public V getAndRemove(final K key)
+    {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try
+        {
+            return delegate.getAndRemove(key);
+        }
+        finally
+        {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public boolean replace(final K key, final V oldValue, final V newValue)
+    {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try
+        {
+            return delegate.replace(key, oldValue, newValue);
+        }
+        finally
+        {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public boolean replace(final K key, final V value)
+    {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try
+        {
+            return delegate.replace(key, value);
+        }
+        finally
+        {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public V getAndReplace(final K key, final V value)
+    {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try
+        {
+            return delegate.getAndReplace(key, value);
+        }
+        finally
+        {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public void removeAll(final Set<? extends K> keys)
+    {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try
+        {
+            delegate.removeAll(keys);
+        }
+        finally
+        {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public void removeAll()
+    {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try
+        {
+            delegate.removeAll();
+        }
+        finally
+        {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public void clear()
+    {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try
+        {
+            delegate.clear();
+        }
+        finally
+        {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public <C extends Configuration<K, V>> C getConfiguration(final Class<C> clazz)
+    {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try
+        {
+            return delegate.getConfiguration(clazz);
+        }
+        finally
+        {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public <T> T invoke(final K key, final EntryProcessor<K, V, T> entryProcessor, final Object... arguments) throws EntryProcessorException
+    {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try
+        {
+            return delegate.invoke(key, entryProcessor, arguments);
+        }
+        finally
+        {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public <T> Map<K, EntryProcessorResult<T>> invokeAll(Set<? extends K> keys, EntryProcessor<K, V, T> entryProcessor, Object... arguments)
+    {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try
+        {
+            return delegate.invokeAll(keys, entryProcessor, arguments);
+        }
+        finally
+        {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public String getName()
+    {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try
+        {
+            return delegate.getName();
+        }
+        finally
+        {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public CacheManager getCacheManager()
+    {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try
+        {
+            return delegate.getCacheManager();
+        }
+        finally
+        {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public void close()
+    {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try
+        {
+            delegate.close();
+        }
+        finally
+        {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public boolean isClosed()
+    {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try
+        {
+            return delegate.isClosed();
+        }
+        finally
+        {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public <T> T unwrap(final Class<T> clazz)
+    {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try
+        {
+            return delegate.unwrap(clazz);
+        }
+        finally
+        {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public void registerCacheEntryListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration)
+    {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try
+        {
+            delegate.registerCacheEntryListener(cacheEntryListenerConfiguration);
+        }
+        finally
+        {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public void deregisterCacheEntryListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration)
+    {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try
+        {
+            delegate.deregisterCacheEntryListener(cacheEntryListenerConfiguration);
+        }
+        finally
+        {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public Iterator<Entry<K, V>> iterator()
+    {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try
+        {
+            return delegate.iterator();
+        }
+        finally
+        {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public boolean equals(final Object obj)
+    {
+        if (ClassLoaderAwareCache.class.isInstance(obj))
+        {
+            return delegate.equals(ClassLoaderAwareCache.class.cast(obj).delegate);
+        }
+        return super.equals(obj);
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return delegate.hashCode();
+    }
+
+    public static <K extends Serializable, V extends Serializable> Cache<K, V> wrap(final ClassLoader loader, final JCSCache<K, V> delegate)
+    {
+        ClassLoader dontWrapLoader = ClassLoaderAwareCache.class.getClassLoader();
+        while (dontWrapLoader != null)
+        {
+            if (loader == dontWrapLoader)
+            {
+                return delegate;
+            }
+            dontWrapLoader = dontWrapLoader.getParent();
+        }
+        return new ClassLoaderAwareCache<>(loader, delegate);
+    }
+
+    public static <K extends Serializable, V extends Serializable> JCSCache<K, V> getDelegate(final Cache<?, ?> cache)
+    {
+        if (JCSCache.class.isInstance(cache))
+        {
+            return (JCSCache<K, V>) cache;
+        }
+        return ((ClassLoaderAwareCache<K, V>) cache).delegate;
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/proxy/ExceptionWrapperHandler.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/proxy/ExceptionWrapperHandler.java
new file mode 100644
index 0000000..056cc93
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/proxy/ExceptionWrapperHandler.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.proxy;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+public class ExceptionWrapperHandler<T> implements InvocationHandler
+{
+    private final T delegate;
+    private final Constructor<? extends RuntimeException> wrapper;
+
+    public ExceptionWrapperHandler(final T delegate, final Class<? extends RuntimeException> exceptionType)
+    {
+        this.delegate = delegate;
+        try
+        {
+            this.wrapper = exceptionType.getConstructor(Throwable.class);
+        }
+        catch (final NoSuchMethodException e)
+        {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    @Override
+    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable
+    {
+        try
+        {
+            return method.invoke(delegate, args);
+        }
+        catch (final InvocationTargetException ite)
+        {
+            final Throwable e = ite.getCause();
+            if (RuntimeException.class.isInstance(e))
+            {
+                final RuntimeException re;
+                try
+                {
+                    re = wrapper.newInstance(e);
+                }
+                catch (final Exception e1)
+                {
+                    throw new IllegalArgumentException(e1);
+                }
+                throw re;
+            }
+            throw e;
+        }
+    }
+
+    public static <T> T newProxy(final ClassLoader loader, final T delegate, final Class<? extends RuntimeException> exceptionType,
+            final Class<T> apis)
+    {
+        return (T) Proxy.newProxyInstance(loader, new Class<?>[] { apis }, new ExceptionWrapperHandler<>(delegate, exceptionType));
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/serialization/Serializations.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/serialization/Serializations.java
new file mode 100644
index 0000000..618738b
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/serialization/Serializations.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.serialization;
+
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+
+public class Serializations
+{
+    public static <K> K copy(final IElementSerializer serializer, final ClassLoader loader, final K key)
+    {
+        try
+        {
+            return serializer.deSerialize(serializer.serialize(key), loader);
+        }
+        catch ( final Exception e)
+        {
+            throw new IllegalStateException(e);
+        }
+    }
+}
diff --git a/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/thread/DaemonThreadFactory.java b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/thread/DaemonThreadFactory.java
new file mode 100644
index 0000000..b24f846
--- /dev/null
+++ b/commons-jcs-jcache/src/main/java/org/apache/commons/jcs3/jcache/thread/DaemonThreadFactory.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache.thread;
+
+
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class DaemonThreadFactory implements ThreadFactory
+{
+    private final AtomicInteger index = new AtomicInteger(1);
+    private final String prefix;
+
+    public DaemonThreadFactory(final String prefix)
+    {
+        this.prefix = prefix;
+    }
+
+    @Override
+    public Thread newThread( final Runnable runner )
+    {
+        final Thread t = new Thread( runner );
+        t.setName(prefix + index.getAndIncrement());
+        t.setDaemon(true);
+        return t;
+    }
+}
\ No newline at end of file
diff --git a/commons-jcs-jcache/src/main/resources/META-INF/services/javax.cache.spi.CachingProvider b/commons-jcs-jcache/src/main/resources/META-INF/services/javax.cache.spi.CachingProvider
index c3eaf99..72078cf 100644
--- a/commons-jcs-jcache/src/main/resources/META-INF/services/javax.cache.spi.CachingProvider
+++ b/commons-jcs-jcache/src/main/resources/META-INF/services/javax.cache.spi.CachingProvider
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 
-org.apache.commons.jcs.jcache.JCSCachingProvider
+org.apache.commons.jcs3.jcache.JCSCachingProvider
diff --git a/commons-jcs-jcache/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/commons-jcs-jcache/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
index a47b35b..164b57e 100644
--- a/commons-jcs-jcache/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
+++ b/commons-jcs-jcache/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
@@ -14,4 +14,4 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-org.apache.commons.jcs.jcache.cdi.MakeJCacheCDIInterceptorFriendly
+org.apache.commons.jcs3.jcache.cdi.MakeJCacheCDIInterceptorFriendly
diff --git a/commons-jcs-jcache/src/test/java/org/apache/commons/jcs/jcache/CacheTest.java b/commons-jcs-jcache/src/test/java/org/apache/commons/jcs/jcache/CacheTest.java
deleted file mode 100644
index 045f41d..0000000
--- a/commons-jcs-jcache/src/test/java/org/apache/commons/jcs/jcache/CacheTest.java
+++ /dev/null
@@ -1,350 +0,0 @@
-package org.apache.commons.jcs.jcache;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.junit.Test;
-
-import javax.cache.Cache;
-import javax.cache.CacheManager;
-import javax.cache.Caching;
-import javax.cache.configuration.CacheEntryListenerConfiguration;
-import javax.cache.configuration.CompleteConfiguration;
-import javax.cache.configuration.Factory;
-import javax.cache.configuration.MutableConfiguration;
-import javax.cache.event.CacheEntryCreatedListener;
-import javax.cache.event.CacheEntryEvent;
-import javax.cache.event.CacheEntryEventFilter;
-import javax.cache.event.CacheEntryListener;
-import javax.cache.event.CacheEntryListenerException;
-import javax.cache.event.CacheEntryRemovedListener;
-import javax.cache.event.CacheEntryUpdatedListener;
-import javax.cache.expiry.AccessedExpiryPolicy;
-import javax.cache.expiry.Duration;
-import javax.cache.expiry.ExpiryPolicy;
-import javax.cache.integration.CacheLoader;
-import javax.cache.integration.CacheLoaderException;
-import javax.cache.integration.CacheWriter;
-import javax.cache.spi.CachingProvider;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-public class CacheTest
-{
-    @Test
-    public void accessExpiry() throws InterruptedException
-    {
-        final CachingProvider cachingProvider = Caching.getCachingProvider();
-        final CacheManager cacheManager = cachingProvider.getCacheManager(cachingProvider.getDefaultURI(),
-                Thread.currentThread().getContextClassLoader(),
-                cachingProvider.getDefaultProperties());
-        final Cache<Integer, Integer> cache = cacheManager.createCache(
-                "test",
-                new MutableConfiguration<Integer, Integer>()
-                        .setStoreByValue(false)
-                        .setStatisticsEnabled(true)
-                        .setManagementEnabled(true)
-                        .setTypes(Integer.class, Integer.class)
-                        .setExpiryPolicyFactory(AccessedExpiryPolicy.factoryOf(new Duration(TimeUnit.MILLISECONDS, 500))));
-
-        try {
-            cache.put(1, 2);
-            cache.get(1);
-            Thread.sleep(650);
-            assertFalse(cache.containsKey(1));
-            cache.put(1, 2);
-            for (int i = 0; i < 3; i++) { // we update the last access to force the idle time and lastaccess to be synced
-                Thread.sleep(250);
-                assertTrue("iteration: " + Integer.toString(i), cache.containsKey(1));
-            }
-            assertTrue(cache.containsKey(1));
-            Thread.sleep(650);
-            assertFalse(cache.containsKey(1));
-        } finally {
-            cacheManager.close();
-            cachingProvider.close();
-        }
-    }
-
-    @Test
-    public void getPut()
-    {
-        final CachingProvider cachingProvider = Caching.getCachingProvider();
-        final CacheManager cacheManager = cachingProvider.getCacheManager();
-        final Cache<String, String> cache = cacheManager.createCache("default", new MutableConfiguration<String, String>());
-        assertFalse(cache.containsKey("foo"));
-        cache.put("foo", "bar");
-        assertTrue(cache.containsKey("foo"));
-        assertEquals("bar", cache.get("foo"));
-        cache.remove("foo");
-        assertFalse(cache.containsKey("foo"));
-        cachingProvider.close();
-    }
-
-    @Test
-    public void listeners()
-    {
-        final CachingProvider cachingProvider = Caching.getCachingProvider();
-        final CacheManager cacheManager = cachingProvider.getCacheManager();
-        cacheManager.createCache("default", new MutableConfiguration<Object, Object>());
-        final Cache<String, String> cache = cacheManager.getCache("default");
-        final Set<String> event = new HashSet<String>();
-        cache.registerCacheEntryListener(new CacheEntryListenerConfiguration<String, String>()
-        {
-            @Override
-            public Factory<CacheEntryListener<? super String, ? super String>> getCacheEntryListenerFactory()
-            {
-                return new Factory<CacheEntryListener<? super String, ? super String>>()
-                {
-                    @Override
-                    public CacheEntryListener<? super String, ? super String> create()
-                    {
-                        return new CacheEntryCreatedListener<String, String>()
-                        {
-                            @Override
-                            public void onCreated(Iterable<CacheEntryEvent<? extends String, ? extends String>> cacheEntryEvents)
-                                    throws CacheEntryListenerException
-                            {
-                                event.add(cacheEntryEvents.iterator().next().getKey());
-                            }
-                        };
-                    }
-                };
-            }
-
-            @Override
-            public boolean isOldValueRequired()
-            {
-                return false;
-            }
-
-            @Override
-            public Factory<CacheEntryEventFilter<? super String, ? super String>> getCacheEntryEventFilterFactory()
-            {
-                return null;
-            }
-
-            @Override
-            public boolean isSynchronous()
-            {
-                return false;
-            }
-        });
-        cache.registerCacheEntryListener(new CacheEntryListenerConfiguration<String, String>()
-        {
-            @Override
-            public Factory<CacheEntryListener<? super String, ? super String>> getCacheEntryListenerFactory()
-            {
-                return new Factory<CacheEntryListener<? super String, ? super String>>()
-                {
-                    @Override
-                    public CacheEntryListener<? super String, ? super String> create()
-                    {
-                        return new CacheEntryUpdatedListener<String, String>()
-                        {
-                            @Override
-                            public void onUpdated(Iterable<CacheEntryEvent<? extends String, ? extends String>> cacheEntryEvents)
-                                    throws CacheEntryListenerException
-                            {
-                                event.add(cacheEntryEvents.iterator().next().getKey());
-                            }
-                        };
-                    }
-                };
-            }
-
-            @Override
-            public boolean isOldValueRequired()
-            {
-                return false;
-            }
-
-            @Override
-            public Factory<CacheEntryEventFilter<? super String, ? super String>> getCacheEntryEventFilterFactory()
-            {
-                return null;
-            }
-
-            @Override
-            public boolean isSynchronous()
-            {
-                return false;
-            }
-        });
-        cache.registerCacheEntryListener(new CacheEntryListenerConfiguration<String, String>()
-        {
-            @Override
-            public Factory<CacheEntryListener<? super String, ? super String>> getCacheEntryListenerFactory()
-            {
-                return new Factory<CacheEntryListener<? super String, ? super String>>()
-                {
-                    @Override
-                    public CacheEntryListener<? super String, ? super String> create()
-                    {
-                        return new CacheEntryRemovedListener<String, String>()
-                        {
-                            @Override
-                            public void onRemoved(Iterable<CacheEntryEvent<? extends String, ? extends String>> cacheEntryEvents)
-                                    throws CacheEntryListenerException
-                            {
-                                event.add(cacheEntryEvents.iterator().next().getKey());
-                            }
-                        };
-                    }
-                };
-            }
-
-            @Override
-            public boolean isOldValueRequired()
-            {
-                return false;
-            }
-
-            @Override
-            public Factory<CacheEntryEventFilter<? super String, ? super String>> getCacheEntryEventFilterFactory()
-            {
-                return null;
-            }
-
-            @Override
-            public boolean isSynchronous()
-            {
-                return false;
-            }
-        });
-
-        cache.put("foo", "bar");
-        assertEquals(1, event.size());
-        assertEquals("foo", event.iterator().next());
-        event.clear();
-        cache.put("foo", "new");
-        assertEquals(1, event.size());
-        assertEquals("foo", event.iterator().next());
-        event.clear();
-        cache.remove("foo");
-        assertEquals(1, event.size());
-        assertEquals("foo", event.iterator().next());
-
-        cachingProvider.close();
-    }
-
-    @Test
-    public void loader()
-    {
-        final CachingProvider cachingProvider = Caching.getCachingProvider();
-        final CacheManager cacheManager = cachingProvider.getCacheManager();
-        cacheManager.createCache("default", new CompleteConfiguration<Object, Object>()
-        {
-            @Override
-            public boolean isReadThrough()
-            {
-                return true;
-            }
-
-            @Override
-            public boolean isWriteThrough()
-            {
-                return false;
-            }
-
-            @Override
-            public boolean isStatisticsEnabled()
-            {
-                return false;
-            }
-
-            @Override
-            public boolean isManagementEnabled()
-            {
-                return false;
-            }
-
-            @Override
-            public Iterable<CacheEntryListenerConfiguration<Object, Object>> getCacheEntryListenerConfigurations()
-            {
-                return null;
-            }
-
-            @Override
-            public Factory<CacheLoader<Object, Object>> getCacheLoaderFactory()
-            {
-                return new Factory<CacheLoader<Object, Object>>()
-                {
-                    @Override
-                    public CacheLoader<Object, Object> create()
-                    {
-                        return new CacheLoader<Object, Object>()
-                        {
-                            @Override
-                            public Object load(Object key) throws CacheLoaderException
-                            {
-                                return "super";
-                            }
-
-                            @Override
-                            public Map<Object, Object> loadAll(Iterable<?> keys) throws CacheLoaderException
-                            {
-                                return null;
-                            }
-                        };
-                    }
-                };
-            }
-
-            @Override
-            public Factory<CacheWriter<? super Object, ? super Object>> getCacheWriterFactory()
-            {
-                return null;
-            }
-
-            @Override
-            public Factory<ExpiryPolicy> getExpiryPolicyFactory()
-            {
-                return null;
-            }
-
-            @Override
-            public Class<Object> getKeyType()
-            {
-                return Object.class;
-            }
-
-            @Override
-            public Class<Object> getValueType()
-            {
-                return Object.class;
-            }
-
-            @Override
-            public boolean isStoreByValue()
-            {
-                return false;
-            }
-        });
-        final Cache<String, String> cache = cacheManager.getCache("default");
-        assertEquals("super", cache.get("lazilyLoaded"));
-        cachingProvider.close();
-    }
-}
diff --git a/commons-jcs-jcache/src/test/java/org/apache/commons/jcs/jcache/CachingProviderTest.java b/commons-jcs-jcache/src/test/java/org/apache/commons/jcs/jcache/CachingProviderTest.java
deleted file mode 100644
index 24234e8..0000000
--- a/commons-jcs-jcache/src/test/java/org/apache/commons/jcs/jcache/CachingProviderTest.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package org.apache.commons.jcs.jcache;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.junit.Test;
-
-import javax.cache.Caching;
-import javax.cache.spi.CachingProvider;
-
-import static org.junit.Assert.assertNotNull;
-
-public class CachingProviderTest
-{
-    @Test
-    public void findProvider()
-    {
-        assertNotNull(Caching.getCachingProvider());
-    }
-
-    @Test
-    public void createCacheMgr()
-    {
-        final CachingProvider cachingProvider = Caching.getCachingProvider();
-        assertNotNull(cachingProvider.getCacheManager());
-        cachingProvider.close();
-    }
-}
diff --git a/commons-jcs-jcache/src/test/java/org/apache/commons/jcs/jcache/ExpiryListenerTest.java b/commons-jcs-jcache/src/test/java/org/apache/commons/jcs/jcache/ExpiryListenerTest.java
deleted file mode 100644
index 1ac2cc1..0000000
--- a/commons-jcs-jcache/src/test/java/org/apache/commons/jcs/jcache/ExpiryListenerTest.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.commons.jcs.jcache;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.concurrent.TimeUnit;
-
-import javax.cache.Cache;
-import javax.cache.CacheManager;
-import javax.cache.Caching;
-import javax.cache.configuration.FactoryBuilder;
-import javax.cache.configuration.MutableCacheEntryListenerConfiguration;
-import javax.cache.configuration.MutableConfiguration;
-import javax.cache.event.CacheEntryEvent;
-import javax.cache.event.CacheEntryExpiredListener;
-import javax.cache.event.CacheEntryListenerException;
-import javax.cache.expiry.CreatedExpiryPolicy;
-import javax.cache.expiry.Duration;
-import javax.cache.expiry.ExpiryPolicy;
-import javax.cache.spi.CachingProvider;
-
-import org.junit.Test;
-
-public class ExpiryListenerTest {
-
-    @Test
-    public void listener() throws InterruptedException {
-        final CachingProvider cachingProvider = Caching.getCachingProvider();
-        final CacheManager cacheManager = cachingProvider.getCacheManager();
-        final CacheEntryExpiredListenerImpl listener = new CacheEntryExpiredListenerImpl();
-        cacheManager.createCache("default", new MutableConfiguration<String, String>()
-                .setExpiryPolicyFactory(new FactoryBuilder.SingletonFactory<ExpiryPolicy>(
-                        new CreatedExpiryPolicy(new Duration(TimeUnit.MILLISECONDS, 1))))
-                .addCacheEntryListenerConfiguration(new MutableCacheEntryListenerConfiguration<String, String>(
-                        FactoryBuilder.factoryOf(listener),
-                        null, false, false
-                )));
-        final Cache<String, String> cache = cacheManager.getCache("default");
-        assertFalse(cache.containsKey("foo"));
-        cache.put("foo", "bar");
-        Thread.sleep(10);
-        assertFalse(cache.containsKey("foo"));
-        cachingProvider.close();
-        assertEquals(1, listener.events.size());
-    }
-
-    private static class CacheEntryExpiredListenerImpl implements CacheEntryExpiredListener<String, String>, Serializable {
-        private final Collection<CacheEntryEvent<? extends String, ? extends String>> events =
-                new ArrayList<CacheEntryEvent<? extends String, ? extends String>>();
-
-        @Override
-        public void onExpired(final Iterable<CacheEntryEvent<? extends String, ? extends String>> cacheEntryEvents)
-                throws CacheEntryListenerException {
-            for (final CacheEntryEvent<? extends String, ? extends String> cacheEntryEvent : cacheEntryEvents) {
-                events.add(cacheEntryEvent);
-            }
-        }
-    }
-}
diff --git a/commons-jcs-jcache/src/test/java/org/apache/commons/jcs/jcache/ImmediateExpiryTest.java b/commons-jcs-jcache/src/test/java/org/apache/commons/jcs/jcache/ImmediateExpiryTest.java
deleted file mode 100644
index bd4d445..0000000
--- a/commons-jcs-jcache/src/test/java/org/apache/commons/jcs/jcache/ImmediateExpiryTest.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.commons.jcs.jcache;
-
-import org.junit.Test;
-
-import javax.cache.Cache;
-import javax.cache.CacheManager;
-import javax.cache.Caching;
-import javax.cache.configuration.FactoryBuilder;
-import javax.cache.configuration.MutableConfiguration;
-import javax.cache.expiry.CreatedExpiryPolicy;
-import javax.cache.expiry.Duration;
-import javax.cache.expiry.ExpiryPolicy;
-import javax.cache.spi.CachingProvider;
-
-import static org.junit.Assert.assertFalse;
-
-public class ImmediateExpiryTest
-{
-    @Test
-    public void immediate()
-    {
-        final CachingProvider cachingProvider = Caching.getCachingProvider();
-        final CacheManager cacheManager = cachingProvider.getCacheManager();
-        cacheManager.createCache("default",
-                new MutableConfiguration<Object, Object>()
-                        .setExpiryPolicyFactory(
-                                new FactoryBuilder.SingletonFactory<ExpiryPolicy>(new CreatedExpiryPolicy(Duration.ZERO))));
-        final Cache<String, String> cache = cacheManager.getCache("default");
-        assertFalse(cache.containsKey("foo"));
-        cache.put("foo", "bar");
-        assertFalse(cache.containsKey("foo"));
-        cachingProvider.close();
-    }
-}
diff --git a/commons-jcs-jcache/src/test/java/org/apache/commons/jcs/jcache/NotSerializableTest.java b/commons-jcs-jcache/src/test/java/org/apache/commons/jcs/jcache/NotSerializableTest.java
deleted file mode 100644
index dc9e729..0000000
--- a/commons-jcs-jcache/src/test/java/org/apache/commons/jcs/jcache/NotSerializableTest.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache;
-
-import org.junit.Test;
-
-import javax.cache.Cache;
-import javax.cache.CacheManager;
-import javax.cache.Caching;
-import javax.cache.configuration.MutableConfiguration;
-import javax.cache.spi.CachingProvider;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-public class NotSerializableTest
-{
-    @Test
-    public void run()
-    {
-        final CachingProvider cachingProvider = Caching.getCachingProvider();
-        final CacheManager cacheManager = cachingProvider.getCacheManager();
-        cacheManager.createCache("default", new MutableConfiguration<String, NotSerializableAndImHappyWithIt>().setStoreByValue(false));
-        final Cache<String, NotSerializableAndImHappyWithIt> cache = cacheManager.getCache("default");
-        assertFalse(cache.containsKey("foo"));
-        cache.put("foo", new NotSerializableAndImHappyWithIt("bar"));
-        assertTrue(cache.containsKey("foo"));
-        assertEquals("bar", cache.get("foo").name);
-        cache.remove("foo");
-        assertFalse(cache.containsKey("foo"));
-        cache.close();
-        cacheManager.close();
-        cachingProvider.close();
-    }
-
-    public static class NotSerializableAndImHappyWithIt {
-        private final String name;
-
-        public NotSerializableAndImHappyWithIt(final String name)
-        {
-            this.name = name;
-        }
-    }
-}
diff --git a/commons-jcs-jcache/src/test/java/org/apache/commons/jcs/jcache/cdi/CDIJCacheHelperTest.java b/commons-jcs-jcache/src/test/java/org/apache/commons/jcs/jcache/cdi/CDIJCacheHelperTest.java
deleted file mode 100644
index bf5a014..0000000
--- a/commons-jcs-jcache/src/test/java/org/apache/commons/jcs/jcache/cdi/CDIJCacheHelperTest.java
+++ /dev/null
@@ -1,140 +0,0 @@
-package org.apache.commons.jcs.jcache.cdi;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-import java.util.Map;
-import javax.cache.annotation.CacheDefaults;
-import javax.cache.annotation.CacheResult;
-import javax.interceptor.InvocationContext;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-import org.junit.Test;
-
-public class CDIJCacheHelperTest
-{
-    @Test
-    public void proxyCacheDefaults()
-    {
-        final CDIJCacheHelper helper = new CDIJCacheHelper();
-
-        final MyParent child1 = MyParent.class.cast(Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
-                new Class<?>[]{MyChild1.class}, new InvocationHandler()
-                {
-                    @Override
-                    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable
-                    {
-                        return null;
-                    }
-                }));
-        final CDIJCacheHelper.MethodMeta meta1 = helper.findMeta(newContext(child1));
-        assertEquals("child", meta1.getCacheResultCacheName());
-
-        final MyParent child2 = MyParent.class.cast(Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
-                new Class<?>[]{MyChild2.class}, new InvocationHandler()
-                {
-                    @Override
-                    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable
-                    {
-                        return null;
-                    }
-                }));
-        final CDIJCacheHelper.MethodMeta meta2 = helper.findMeta(newContext(child2));
-        assertEquals("child2", meta2.getCacheResultCacheName());
-    }
-
-    private InvocationContext newContext(final MyParent child1) {
-        return new InvocationContext()
-        {
-            @Override
-            public Object getTarget()
-            {
-                return child1;
-            }
-
-            @Override
-            public Method getMethod()
-            {
-                try {
-                    return MyParent.class.getMethod("foo");
-                } catch (NoSuchMethodException e) {
-                    fail(e.getMessage());
-                    return null;
-                }
-            }
-
-            @Override
-            public Constructor<?> getConstructor()
-            {
-                return null;
-            }
-
-            @Override
-            public Object[] getParameters()
-            {
-                return new Object[0];
-            }
-
-            @Override
-            public void setParameters(final Object[] objects)
-            {
-
-            }
-
-            @Override
-            public Map<String, Object> getContextData()
-            {
-                return null;
-            }
-
-            @Override
-            public Object proceed() throws Exception
-            {
-                return null;
-            }
-
-            @Override
-            public Object getTimer()
-            {
-                return null;
-            }
-        };
-    }
-
-    public interface MyParent
-    {
-        @CacheResult
-        String foo();
-    }
-
-    @CacheDefaults(cacheName = "child")
-    public interface MyChild1 extends MyParent
-    {
-    }
-
-    @CacheDefaults(cacheName = "child2")
-    public interface MyChild2 extends MyParent
-    {
-    }
-}
diff --git a/commons-jcs-jcache/src/test/java/org/apache/commons/jcs3/jcache/CacheTest.java b/commons-jcs-jcache/src/test/java/org/apache/commons/jcs3/jcache/CacheTest.java
new file mode 100644
index 0000000..dfb5f70
--- /dev/null
+++ b/commons-jcs-jcache/src/test/java/org/apache/commons/jcs3/jcache/CacheTest.java
@@ -0,0 +1,298 @@
+package org.apache.commons.jcs3.jcache;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.junit.Test;
+
+import javax.cache.Cache;
+import javax.cache.CacheManager;
+import javax.cache.Caching;
+import javax.cache.configuration.CacheEntryListenerConfiguration;
+import javax.cache.configuration.CompleteConfiguration;
+import javax.cache.configuration.Factory;
+import javax.cache.configuration.MutableConfiguration;
+import javax.cache.event.CacheEntryCreatedListener;
+import javax.cache.event.CacheEntryEvent;
+import javax.cache.event.CacheEntryEventFilter;
+import javax.cache.event.CacheEntryListener;
+import javax.cache.event.CacheEntryListenerException;
+import javax.cache.event.CacheEntryRemovedListener;
+import javax.cache.event.CacheEntryUpdatedListener;
+import javax.cache.expiry.AccessedExpiryPolicy;
+import javax.cache.expiry.Duration;
+import javax.cache.expiry.ExpiryPolicy;
+import javax.cache.integration.CacheLoader;
+import javax.cache.integration.CacheLoaderException;
+import javax.cache.integration.CacheWriter;
+import javax.cache.spi.CachingProvider;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class CacheTest
+{
+    @Test
+    public void accessExpiry() throws InterruptedException
+    {
+        final CachingProvider cachingProvider = Caching.getCachingProvider();
+        final CacheManager cacheManager = cachingProvider.getCacheManager(cachingProvider.getDefaultURI(),
+                Thread.currentThread().getContextClassLoader(),
+                cachingProvider.getDefaultProperties());
+        final Cache<Integer, Integer> cache = cacheManager.createCache(
+                "test",
+                new MutableConfiguration<Integer, Integer>()
+                        .setStoreByValue(false)
+                        .setStatisticsEnabled(true)
+                        .setManagementEnabled(true)
+                        .setTypes(Integer.class, Integer.class)
+                        .setExpiryPolicyFactory(AccessedExpiryPolicy.factoryOf(new Duration(TimeUnit.MILLISECONDS, 500))));
+
+        try {
+            cache.put(1, 2);
+            cache.get(1);
+            Thread.sleep(650);
+            assertFalse(cache.containsKey(1));
+            cache.put(1, 2);
+            for (int i = 0; i < 3; i++) { // we update the last access to force the idle time and lastaccess to be synced
+                Thread.sleep(250);
+                assertTrue("iteration: " + Integer.toString(i), cache.containsKey(1));
+            }
+            assertTrue(cache.containsKey(1));
+            Thread.sleep(650);
+            assertFalse(cache.containsKey(1));
+        } finally {
+            cacheManager.close();
+            cachingProvider.close();
+        }
+    }
+
+    @Test
+    public void getPut()
+    {
+        final CachingProvider cachingProvider = Caching.getCachingProvider();
+        final CacheManager cacheManager = cachingProvider.getCacheManager();
+        final Cache<String, String> cache = cacheManager.createCache("default", new MutableConfiguration<String, String>());
+        assertFalse(cache.containsKey("foo"));
+        cache.put("foo", "bar");
+        assertTrue(cache.containsKey("foo"));
+        assertEquals("bar", cache.get("foo"));
+        cache.remove("foo");
+        assertFalse(cache.containsKey("foo"));
+        cachingProvider.close();
+    }
+
+    @Test
+    public void listeners()
+    {
+        final CachingProvider cachingProvider = Caching.getCachingProvider();
+        final CacheManager cacheManager = cachingProvider.getCacheManager();
+        cacheManager.createCache("default", new MutableConfiguration<>());
+        final Cache<String, String> cache = cacheManager.getCache("default");
+        final Set<String> event = new HashSet<>();
+        cache.registerCacheEntryListener(new CacheEntryListenerConfiguration<String, String>()
+        {
+            @Override
+            public Factory<CacheEntryListener<? super String, ? super String>> getCacheEntryListenerFactory()
+            {
+                return () -> (CacheEntryCreatedListener<String, String>) cacheEntryEvents -> event.add(cacheEntryEvents.iterator().next().getKey());
+            }
+
+            @Override
+            public boolean isOldValueRequired()
+            {
+                return false;
+            }
+
+            @Override
+            public Factory<CacheEntryEventFilter<? super String, ? super String>> getCacheEntryEventFilterFactory()
+            {
+                return null;
+            }
+
+            @Override
+            public boolean isSynchronous()
+            {
+                return false;
+            }
+        });
+        cache.registerCacheEntryListener(new CacheEntryListenerConfiguration<String, String>()
+        {
+            @Override
+            public Factory<CacheEntryListener<? super String, ? super String>> getCacheEntryListenerFactory()
+            {
+                return () -> (CacheEntryUpdatedListener<String, String>) cacheEntryEvents -> event.add(cacheEntryEvents.iterator().next().getKey());
+            }
+
+            @Override
+            public boolean isOldValueRequired()
+            {
+                return false;
+            }
+
+            @Override
+            public Factory<CacheEntryEventFilter<? super String, ? super String>> getCacheEntryEventFilterFactory()
+            {
+                return null;
+            }
+
+            @Override
+            public boolean isSynchronous()
+            {
+                return false;
+            }
+        });
+        cache.registerCacheEntryListener(new CacheEntryListenerConfiguration<String, String>()
+        {
+            @Override
+            public Factory<CacheEntryListener<? super String, ? super String>> getCacheEntryListenerFactory()
+            {
+                return () -> (CacheEntryRemovedListener<String, String>) cacheEntryEvents -> event.add(cacheEntryEvents.iterator().next().getKey());
+            }
+
+            @Override
+            public boolean isOldValueRequired()
+            {
+                return false;
+            }
+
+            @Override
+            public Factory<CacheEntryEventFilter<? super String, ? super String>> getCacheEntryEventFilterFactory()
+            {
+                return null;
+            }
+
+            @Override
+            public boolean isSynchronous()
+            {
+                return false;
+            }
+        });
+
+        cache.put("foo", "bar");
+        assertEquals(1, event.size());
+        assertEquals("foo", event.iterator().next());
+        event.clear();
+        cache.put("foo", "new");
+        assertEquals(1, event.size());
+        assertEquals("foo", event.iterator().next());
+        event.clear();
+        cache.remove("foo");
+        assertEquals(1, event.size());
+        assertEquals("foo", event.iterator().next());
+
+        cachingProvider.close();
+    }
+
+    @Test
+    public void loader()
+    {
+        final CachingProvider cachingProvider = Caching.getCachingProvider();
+        final CacheManager cacheManager = cachingProvider.getCacheManager();
+        cacheManager.createCache("default", new CompleteConfiguration<Object, Object>()
+        {
+            @Override
+            public boolean isReadThrough()
+            {
+                return true;
+            }
+
+            @Override
+            public boolean isWriteThrough()
+            {
+                return false;
+            }
+
+            @Override
+            public boolean isStatisticsEnabled()
+            {
+                return false;
+            }
+
+            @Override
+            public boolean isManagementEnabled()
+            {
+                return false;
+            }
+
+            @Override
+            public Iterable<CacheEntryListenerConfiguration<Object, Object>> getCacheEntryListenerConfigurations()
+            {
+                return null;
+            }
+
+            @Override
+            public Factory<CacheLoader<Object, Object>> getCacheLoaderFactory()
+            {
+                return () -> new CacheLoader<Object, Object>()
+                {
+                    @Override
+                    public Object load(Object key) throws CacheLoaderException
+                    {
+                        return "super";
+                    }
+
+                    @Override
+                    public Map<Object, Object> loadAll(Iterable<?> keys) throws CacheLoaderException
+                    {
+                        return null;
+                    }
+                };
+            }
+
+            @Override
+            public Factory<CacheWriter<? super Object, ? super Object>> getCacheWriterFactory()
+            {
+                return null;
+            }
+
+            @Override
+            public Factory<ExpiryPolicy> getExpiryPolicyFactory()
+            {
+                return null;
+            }
+
+            @Override
+            public Class<Object> getKeyType()
+            {
+                return Object.class;
+            }
+
+            @Override
+            public Class<Object> getValueType()
+            {
+                return Object.class;
+            }
+
+            @Override
+            public boolean isStoreByValue()
+            {
+                return false;
+            }
+        });
+        final Cache<String, String> cache = cacheManager.getCache("default");
+        assertEquals("super", cache.get("lazilyLoaded"));
+        cachingProvider.close();
+    }
+}
diff --git a/commons-jcs-jcache/src/test/java/org/apache/commons/jcs3/jcache/CachingProviderTest.java b/commons-jcs-jcache/src/test/java/org/apache/commons/jcs3/jcache/CachingProviderTest.java
new file mode 100644
index 0000000..5e28cf4
--- /dev/null
+++ b/commons-jcs-jcache/src/test/java/org/apache/commons/jcs3/jcache/CachingProviderTest.java
@@ -0,0 +1,44 @@
+package org.apache.commons.jcs3.jcache;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.junit.Test;
+
+import javax.cache.Caching;
+import javax.cache.spi.CachingProvider;
+
+import static org.junit.Assert.assertNotNull;
+
+public class CachingProviderTest
+{
+    @Test
+    public void findProvider()
+    {
+        assertNotNull(Caching.getCachingProvider());
+    }
+
+    @Test
+    public void createCacheMgr()
+    {
+        final CachingProvider cachingProvider = Caching.getCachingProvider();
+        assertNotNull(cachingProvider.getCacheManager());
+        cachingProvider.close();
+    }
+}
diff --git a/commons-jcs-jcache/src/test/java/org/apache/commons/jcs3/jcache/ExpiryListenerTest.java b/commons-jcs-jcache/src/test/java/org/apache/commons/jcs3/jcache/ExpiryListenerTest.java
new file mode 100644
index 0000000..2dd6e6f
--- /dev/null
+++ b/commons-jcs-jcache/src/test/java/org/apache/commons/jcs3/jcache/ExpiryListenerTest.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.commons.jcs3.jcache;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.concurrent.TimeUnit;
+
+import javax.cache.Cache;
+import javax.cache.CacheManager;
+import javax.cache.Caching;
+import javax.cache.configuration.FactoryBuilder;
+import javax.cache.configuration.MutableCacheEntryListenerConfiguration;
+import javax.cache.configuration.MutableConfiguration;
+import javax.cache.event.CacheEntryEvent;
+import javax.cache.event.CacheEntryExpiredListener;
+import javax.cache.event.CacheEntryListenerException;
+import javax.cache.expiry.CreatedExpiryPolicy;
+import javax.cache.expiry.Duration;
+import javax.cache.expiry.ExpiryPolicy;
+import javax.cache.spi.CachingProvider;
+
+import org.junit.Test;
+
+public class ExpiryListenerTest {
+
+    @Test
+    public void listener() throws InterruptedException {
+        final CachingProvider cachingProvider = Caching.getCachingProvider();
+        final CacheManager cacheManager = cachingProvider.getCacheManager();
+        final CacheEntryExpiredListenerImpl listener = new CacheEntryExpiredListenerImpl();
+        cacheManager.createCache("default", new MutableConfiguration<String, String>()
+                .setExpiryPolicyFactory(new FactoryBuilder.SingletonFactory<ExpiryPolicy>(
+                        new CreatedExpiryPolicy(new Duration(TimeUnit.MILLISECONDS, 1))))
+                .addCacheEntryListenerConfiguration(new MutableCacheEntryListenerConfiguration<>(
+                        FactoryBuilder.factoryOf(listener),
+                        null, false, false
+                )));
+        final Cache<String, String> cache = cacheManager.getCache("default");
+        assertFalse(cache.containsKey("foo"));
+        cache.put("foo", "bar");
+        Thread.sleep(10);
+        assertFalse(cache.containsKey("foo"));
+        cachingProvider.close();
+        assertEquals(1, listener.events.size());
+    }
+
+    private static class CacheEntryExpiredListenerImpl implements CacheEntryExpiredListener<String, String>, Serializable {
+        private final Collection<CacheEntryEvent<? extends String, ? extends String>> events =
+                new ArrayList<>();
+
+        @Override
+        public void onExpired(final Iterable<CacheEntryEvent<? extends String, ? extends String>> cacheEntryEvents)
+                throws CacheEntryListenerException {
+            for (final CacheEntryEvent<? extends String, ? extends String> cacheEntryEvent : cacheEntryEvents) {
+                events.add(cacheEntryEvent);
+            }
+        }
+    }
+}
diff --git a/commons-jcs-jcache/src/test/java/org/apache/commons/jcs3/jcache/ImmediateExpiryTest.java b/commons-jcs-jcache/src/test/java/org/apache/commons/jcs3/jcache/ImmediateExpiryTest.java
new file mode 100644
index 0000000..a3b372b
--- /dev/null
+++ b/commons-jcs-jcache/src/test/java/org/apache/commons/jcs3/jcache/ImmediateExpiryTest.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.commons.jcs3.jcache;
+
+import org.junit.Test;
+
+import javax.cache.Cache;
+import javax.cache.CacheManager;
+import javax.cache.Caching;
+import javax.cache.configuration.FactoryBuilder;
+import javax.cache.configuration.MutableConfiguration;
+import javax.cache.expiry.CreatedExpiryPolicy;
+import javax.cache.expiry.Duration;
+import javax.cache.expiry.ExpiryPolicy;
+import javax.cache.spi.CachingProvider;
+
+import static org.junit.Assert.assertFalse;
+
+public class ImmediateExpiryTest
+{
+    @Test
+    public void immediate()
+    {
+        final CachingProvider cachingProvider = Caching.getCachingProvider();
+        final CacheManager cacheManager = cachingProvider.getCacheManager();
+        cacheManager.createCache("default",
+                new MutableConfiguration<>()
+                        .setExpiryPolicyFactory(
+                                new FactoryBuilder.SingletonFactory<ExpiryPolicy>(new CreatedExpiryPolicy(Duration.ZERO))));
+        final Cache<String, String> cache = cacheManager.getCache("default");
+        assertFalse(cache.containsKey("foo"));
+        cache.put("foo", "bar");
+        assertFalse(cache.containsKey("foo"));
+        cachingProvider.close();
+    }
+}
diff --git a/commons-jcs-jcache/src/test/java/org/apache/commons/jcs3/jcache/NotSerializableTest.java b/commons-jcs-jcache/src/test/java/org/apache/commons/jcs3/jcache/NotSerializableTest.java
new file mode 100644
index 0000000..7ce2526
--- /dev/null
+++ b/commons-jcs-jcache/src/test/java/org/apache/commons/jcs3/jcache/NotSerializableTest.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache;
+
+import org.junit.Test;
+
+import javax.cache.Cache;
+import javax.cache.CacheManager;
+import javax.cache.Caching;
+import javax.cache.configuration.MutableConfiguration;
+import javax.cache.spi.CachingProvider;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class NotSerializableTest
+{
+    @Test
+    public void run()
+    {
+        final CachingProvider cachingProvider = Caching.getCachingProvider();
+        final CacheManager cacheManager = cachingProvider.getCacheManager();
+        cacheManager.createCache("default", new MutableConfiguration<String, NotSerializableAndImHappyWithIt>().setStoreByValue(false));
+        final Cache<String, NotSerializableAndImHappyWithIt> cache = cacheManager.getCache("default");
+        assertFalse(cache.containsKey("foo"));
+        cache.put("foo", new NotSerializableAndImHappyWithIt("bar"));
+        assertTrue(cache.containsKey("foo"));
+        assertEquals("bar", cache.get("foo").name);
+        cache.remove("foo");
+        assertFalse(cache.containsKey("foo"));
+        cache.close();
+        cacheManager.close();
+        cachingProvider.close();
+    }
+
+    public static class NotSerializableAndImHappyWithIt {
+        private final String name;
+
+        public NotSerializableAndImHappyWithIt(final String name)
+        {
+            this.name = name;
+        }
+    }
+}
diff --git a/commons-jcs-jcache/src/test/java/org/apache/commons/jcs3/jcache/cdi/CDIJCacheHelperTest.java b/commons-jcs-jcache/src/test/java/org/apache/commons/jcs3/jcache/cdi/CDIJCacheHelperTest.java
new file mode 100644
index 0000000..bf67273
--- /dev/null
+++ b/commons-jcs-jcache/src/test/java/org/apache/commons/jcs3/jcache/cdi/CDIJCacheHelperTest.java
@@ -0,0 +1,127 @@
+package org.apache.commons.jcs3.jcache.cdi;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Map;
+import javax.cache.annotation.CacheDefaults;
+import javax.cache.annotation.CacheResult;
+import javax.interceptor.InvocationContext;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import org.apache.commons.jcs3.jcache.cdi.CDIJCacheHelper;
+import org.junit.Test;
+
+public class CDIJCacheHelperTest
+{
+    @Test
+    public void proxyCacheDefaults()
+    {
+        final CDIJCacheHelper helper = new CDIJCacheHelper();
+
+        final MyParent child1 = MyParent.class.cast(Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
+                new Class<?>[]{MyChild1.class}, (proxy, method, args) -> null));
+        final CDIJCacheHelper.MethodMeta meta1 = helper.findMeta(newContext(child1));
+        assertEquals("child", meta1.getCacheResultCacheName());
+
+        final MyParent child2 = MyParent.class.cast(Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
+                new Class<?>[]{MyChild2.class}, (proxy, method, args) -> null));
+        final CDIJCacheHelper.MethodMeta meta2 = helper.findMeta(newContext(child2));
+        assertEquals("child2", meta2.getCacheResultCacheName());
+    }
+
+    private InvocationContext newContext(final MyParent child1) {
+        return new InvocationContext()
+        {
+            @Override
+            public Object getTarget()
+            {
+                return child1;
+            }
+
+            @Override
+            public Method getMethod()
+            {
+                try {
+                    return MyParent.class.getMethod("foo");
+                } catch (NoSuchMethodException e) {
+                    fail(e.getMessage());
+                    return null;
+                }
+            }
+
+            @Override
+            public Constructor<?> getConstructor()
+            {
+                return null;
+            }
+
+            @Override
+            public Object[] getParameters()
+            {
+                return new Object[0];
+            }
+
+            @Override
+            public void setParameters(final Object[] objects)
+            {
+
+            }
+
+            @Override
+            public Map<String, Object> getContextData()
+            {
+                return null;
+            }
+
+            @Override
+            public Object proceed() throws Exception
+            {
+                return null;
+            }
+
+            @Override
+            public Object getTimer()
+            {
+                return null;
+            }
+        };
+    }
+
+    public interface MyParent
+    {
+        @CacheResult
+        String foo();
+    }
+
+    @CacheDefaults(cacheName = "child")
+    public interface MyChild1 extends MyParent
+    {
+    }
+
+    @CacheDefaults(cacheName = "child2")
+    public interface MyChild2 extends MyParent
+    {
+    }
+}
diff --git a/commons-jcs-sandbox/pom.xml b/commons-jcs-sandbox/pom.xml
index a302cfb..d76dd5f 100644
--- a/commons-jcs-sandbox/pom.xml
+++ b/commons-jcs-sandbox/pom.xml
@@ -23,7 +23,7 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <groupId>org.apache.commons</groupId>
-    <artifactId>commons-jcs</artifactId>
+    <artifactId>commons-jcs3</artifactId>
     <version>3.0-SNAPSHOT</version>
   </parent>
 
diff --git a/commons-jcs-tck-tests/pom.xml b/commons-jcs-tck-tests/pom.xml
index 6d2dade..88123f9 100644
--- a/commons-jcs-tck-tests/pom.xml
+++ b/commons-jcs-tck-tests/pom.xml
@@ -22,7 +22,7 @@
 
   <parent>
     <groupId>org.apache.commons</groupId>
-    <artifactId>commons-jcs</artifactId>
+    <artifactId>commons-jcs3</artifactId>
     <version>3.0-SNAPSHOT</version>
     <relativePath>../pom.xml</relativePath>
   </parent>
@@ -30,10 +30,10 @@
   <!--
 
   to run from an IDE a JMX test add:
-    -Dorg.jsr107.tck.management.agentId=MBeanServerJCS -Djavax.management.builder.initial=org.apache.commons.jcs.jcache.jmx.ConfigurableMBeanServerIdBuilder
+    -Dorg.jsr107.tck.management.agentId=MBeanServerJCS -Djavax.management.builder.initial=org.apache.commons.jcs3.jcache.jmx.ConfigurableMBeanServerIdBuilder
   -->
 
-  <artifactId>commons-jcs-jcache-tck</artifactId>
+  <artifactId>commons-jcs3-jcache-tck</artifactId>
   <version>3.0-SNAPSHOT</version>
 
   <name>Apache Commons JCS :: JCache TCK</name>
@@ -47,14 +47,14 @@
   <properties>
 
     <implementation-groupId>${project.groupId}</implementation-groupId>
-    <implementation-artifactId>commons-jcs</implementation-artifactId>
+    <implementation-artifactId>commons-jcs3</implementation-artifactId>
     <implementation-version>${project.version}</implementation-version>
 
-    <CacheManagerImpl>org.apache.commons.jcs.jcache.JCSCachingManager</CacheManagerImpl>
-    <CacheImpl>org.apache.commons.jcs.jcache.JCSCache</CacheImpl>
-    <CacheEntryImpl>org.apache.commons.jcs.jcache.JCSEntry</CacheEntryImpl>
+    <CacheManagerImpl>org.apache.commons.jcs3.jcache.JCSCachingManager</CacheManagerImpl>
+    <CacheImpl>org.apache.commons.jcs3.jcache.JCSCache</CacheImpl>
+    <CacheEntryImpl>org.apache.commons.jcs3.jcache.JCSEntry</CacheEntryImpl>
 
-    <javax.management.builder.initial>org.apache.commons.jcs.jcache.jmx.ConfigurableMBeanServerIdBuilder</javax.management.builder.initial>
+    <javax.management.builder.initial>org.apache.commons.jcs3.jcache.jmx.ConfigurableMBeanServerIdBuilder</javax.management.builder.initial>
     <org.jsr107.tck.management.agentId>MBeanServerJCS</org.jsr107.tck.management.agentId>
 
     <domain-lib-dir>${project.build.directory}/domainlib</domain-lib-dir>
@@ -64,12 +64,12 @@
   <dependencies>
     <dependency>
       <groupId>${project.groupId}</groupId>
-      <artifactId>commons-jcs-core</artifactId>
+      <artifactId>commons-jcs3-core</artifactId>
     </dependency>
 
     <dependency>
       <groupId>${project.groupId}</groupId>
-      <artifactId>commons-jcs-jcache</artifactId>
+      <artifactId>commons-jcs3-jcache</artifactId>
     </dependency>
 
     <dependency>
diff --git a/commons-jcs-tck-tests/src/test/java/org/apache/commons/jcs/jcache/EnsureCDIIsTestedWhenTCKsRunTest.java b/commons-jcs-tck-tests/src/test/java/org/apache/commons/jcs/jcache/EnsureCDIIsTestedWhenTCKsRunTest.java
deleted file mode 100644
index fdcab32..0000000
--- a/commons-jcs-tck-tests/src/test/java/org/apache/commons/jcs/jcache/EnsureCDIIsTestedWhenTCKsRunTest.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache;
-
-import org.junit.Test;
-
-import javax.cache.annotation.BeanProvider;
-import java.util.Iterator;
-import java.util.ServiceLoader;
-
-import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-
-// useless test but without it we are not sure
-// CDI TCKs passed
-public class EnsureCDIIsTestedWhenTCKsRunTest
-{
-    @Test
-    public void checkOWBProvider()
-    {
-        try {
-            final Iterator<BeanProvider> iterator = ServiceLoader.load(BeanProvider.class).iterator();
-            assertTrue(iterator.hasNext());
-            assertThat(iterator.next(), instanceOf(OWBBeanProvider.class));
-        } catch (java.lang.UnsupportedClassVersionError e) {
-            System.err.println("Ignoring checkOWBProvider test failure on " + System.getProperty("java.version"));
-        }
-    }
-}
diff --git a/commons-jcs-tck-tests/src/test/java/org/apache/commons/jcs/jcache/OWBBeanProvider.java b/commons-jcs-tck-tests/src/test/java/org/apache/commons/jcs/jcache/OWBBeanProvider.java
deleted file mode 100644
index 3863106..0000000
--- a/commons-jcs-tck-tests/src/test/java/org/apache/commons/jcs/jcache/OWBBeanProvider.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.jcs.jcache;
-
-import org.apache.webbeans.config.WebBeansContext;
-import org.apache.webbeans.container.BeanManagerImpl;
-import org.apache.webbeans.spi.ContainerLifecycle;
-
-import java.util.Set;
-import javax.cache.annotation.BeanProvider;
-import javax.enterprise.inject.spi.Bean;
-
-public class OWBBeanProvider implements BeanProvider
-{
-    private final BeanManagerImpl bm;
-
-    public OWBBeanProvider()
-    {
-        final WebBeansContext webBeansContext = WebBeansContext.currentInstance();
-        final ContainerLifecycle lifecycle = webBeansContext.getService(ContainerLifecycle.class);
-        lifecycle.startApplication(null);
-        Runtime.getRuntime().addShutdownHook(new Thread()
-        {
-            @Override
-            public void run()
-            {
-                lifecycle.stopApplication(null);
-            }
-        });
-        bm = webBeansContext.getBeanManagerImpl();
-    }
-
-    @Override
-    public <T> T getBeanByType(final Class<T> tClass)
-    {
-        if (tClass == null)
-        {
-            throw new IllegalArgumentException("no bean class specified");
-        }
-
-        final Set<Bean<?>> beans = bm.getBeans(tClass);
-        if (beans.isEmpty())
-        {
-            throw new IllegalStateException("no bean of type " + tClass.getName());
-        }
-        final Bean<?> bean = bm.resolve(beans);
-        return (T) bm.getReference(bean, bean.getBeanClass(), bm.createCreationalContext(bean));
-    }
-}
diff --git a/commons-jcs-tck-tests/src/test/java/org/apache/commons/jcs3/jcache/EnsureCDIIsTestedWhenTCKsRunTest.java b/commons-jcs-tck-tests/src/test/java/org/apache/commons/jcs3/jcache/EnsureCDIIsTestedWhenTCKsRunTest.java
new file mode 100644
index 0000000..ef1f621
--- /dev/null
+++ b/commons-jcs-tck-tests/src/test/java/org/apache/commons/jcs3/jcache/EnsureCDIIsTestedWhenTCKsRunTest.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache;
+
+import org.junit.Test;
+
+import javax.cache.annotation.BeanProvider;
+import java.util.Iterator;
+import java.util.ServiceLoader;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+// useless test but without it we are not sure
+// CDI TCKs passed
+public class EnsureCDIIsTestedWhenTCKsRunTest
+{
+    @Test
+    public void checkOWBProvider()
+    {
+        try {
+            final Iterator<BeanProvider> iterator = ServiceLoader.load(BeanProvider.class).iterator();
+            assertTrue(iterator.hasNext());
+            assertThat(iterator.next(), instanceOf(OWBBeanProvider.class));
+        } catch (java.lang.UnsupportedClassVersionError e) {
+            System.err.println("Ignoring checkOWBProvider test failure on " + System.getProperty("java.version"));
+        }
+    }
+}
diff --git a/commons-jcs-tck-tests/src/test/java/org/apache/commons/jcs3/jcache/OWBBeanProvider.java b/commons-jcs-tck-tests/src/test/java/org/apache/commons/jcs3/jcache/OWBBeanProvider.java
new file mode 100644
index 0000000..2f0d006
--- /dev/null
+++ b/commons-jcs-tck-tests/src/test/java/org/apache/commons/jcs3/jcache/OWBBeanProvider.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.jcs3.jcache;
+
+import org.apache.webbeans.config.WebBeansContext;
+import org.apache.webbeans.container.BeanManagerImpl;
+import org.apache.webbeans.spi.ContainerLifecycle;
+
+import java.util.Set;
+import javax.cache.annotation.BeanProvider;
+import javax.enterprise.inject.spi.Bean;
+
+public class OWBBeanProvider implements BeanProvider
+{
+    private final BeanManagerImpl bm;
+
+    public OWBBeanProvider()
+    {
+        final WebBeansContext webBeansContext = WebBeansContext.currentInstance();
+        final ContainerLifecycle lifecycle = webBeansContext.getService(ContainerLifecycle.class);
+        lifecycle.startApplication(null);
+        Runtime.getRuntime().addShutdownHook(new Thread()
+        {
+            @Override
+            public void run()
+            {
+                lifecycle.stopApplication(null);
+            }
+        });
+        bm = webBeansContext.getBeanManagerImpl();
+    }
+
+    @Override
+    public <T> T getBeanByType(final Class<T> tClass)
+    {
+        if (tClass == null)
+        {
+            throw new IllegalArgumentException("no bean class specified");
+        }
+
+        final Set<Bean<?>> beans = bm.getBeans(tClass);
+        if (beans.isEmpty())
+        {
+            throw new IllegalStateException("no bean of type " + tClass.getName());
+        }
+        final Bean<?> bean = bm.resolve(beans);
+        return (T) bm.getReference(bean, bean.getBeanClass(), bm.createCreationalContext(bean));
+    }
+}
diff --git a/commons-jcs-tck-tests/src/test/resources/META-INF/services/javax.cache.annotation.BeanProvider b/commons-jcs-tck-tests/src/test/resources/META-INF/services/javax.cache.annotation.BeanProvider
index ee527d8..3af617c 100644
--- a/commons-jcs-tck-tests/src/test/resources/META-INF/services/javax.cache.annotation.BeanProvider
+++ b/commons-jcs-tck-tests/src/test/resources/META-INF/services/javax.cache.annotation.BeanProvider
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 
-org.apache.commons.jcs.jcache.OWBBeanProvider
+org.apache.commons.jcs3.jcache.OWBBeanProvider
diff --git a/commons-jcs-tck-tests/src/test/resources/log4j.properties b/commons-jcs-tck-tests/src/test/resources/log4j.properties
deleted file mode 100644
index 18c5889..0000000
--- a/commons-jcs-tck-tests/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,19 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-log4j.rootCategory=INFO, stdout
-log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.layout=org.apache.log4j.SimpleLayout
diff --git a/pom.xml b/pom.xml
index 3a4bfa6..7768c50 100644
--- a/pom.xml
+++ b/pom.xml
@@ -24,7 +24,7 @@
     <version>50</version>
   </parent>
 
-  <artifactId>commons-jcs</artifactId>
+  <artifactId>commons-jcs3</artifactId>
   <packaging>pom</packaging>
   <version>3.0-SNAPSHOT</version>
 
@@ -290,13 +290,13 @@
 
       <dependency>
         <groupId>${project.groupId}</groupId>
-        <artifactId>commons-jcs-core</artifactId>
+        <artifactId>commons-jcs3-core</artifactId>
         <version>${project.version}</version>
       </dependency>
 
       <dependency>
         <groupId>${project.groupId}</groupId>
-        <artifactId>commons-jcs-core</artifactId>
+        <artifactId>commons-jcs3-core</artifactId>
         <version>${project.version}</version>
         <type>test-jar</type>
         <scope>test</scope>
@@ -304,17 +304,10 @@
 
       <dependency>
         <groupId>${project.groupId}</groupId>
-        <artifactId>commons-jcs-jcache</artifactId>
+        <artifactId>commons-jcs3-jcache</artifactId>
         <version>${project.version}</version>
       </dependency>
 
-      <!--  REQUIRED FOR JCS CORE -->
-      <dependency>
-        <groupId>commons-logging</groupId>
-        <artifactId>commons-logging</artifactId>
-        <version>1.2</version>
-      </dependency>
-
       <!--  JDBC DISK CACHE -->
       <dependency>
         <groupId>org.apache.commons</groupId>
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index e578112..ce7c0a3 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -66,6 +66,14 @@
                 Finally require Java 8
             </action>
         </release>
+        <release version="2.2.1" date="2018-08-21">
+          <action dev="kinow" type="fix" due-to="athun">
+            Unexpected dispose() in CompositeCacheManager.release()
+          </action>
+          <action issue="JCS-183" dev="rmannibucau" type="fix">
+            JCache CDI Integration is slow
+          </action>
+        </release>
         <release version="2.2" date="2017-08-02">
             <action issue="JCS-180" dev="rmannibucau" type="fix">
                 CacheInvocationContextImpl NPE if method doesnt have any argument
diff --git a/src/site/site.xml b/src/site/site.xml
index 83832db..edd1123 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -32,6 +32,7 @@
     <menu name="Development">
       <item name="Release Notes" href="/changes-report.html"/>
       <item name="Upgrading from 1.3 to 2.0" href="/UpgradingFrom13.html"/>
+      <item name="Upgrading from 2.x to 3.0" href="/UpgradingFrom2x.html"/>
       <item name="Mailing Lists" href="/mail-lists.html"/>
       <item name="Issue Tracking" href="/issue-tracking.html"/>
       <item name="Source Repository" href="/source-repository.html"/>
diff --git a/xdocs/UpgradingFrom2x.xml b/xdocs/UpgradingFrom2x.xml
index 70611e5..409fbae 100644
--- a/xdocs/UpgradingFrom2x.xml
+++ b/xdocs/UpgradingFrom2x.xml
@@ -45,9 +45,9 @@
           The Maven coordinates change from
         <source><![CDATA[
 <dependency>
-    <groupId>org.apache.jcs</groupId>
-    <artifactId>jcs</artifactId>
-    <version>1.3</version>
+    <groupId>org.apache.commons.jcs</groupId>
+    <artifactId>commons-jcs-core</artifactId>
+    <version>2.2.1</version>
 </dependency>
 ]]></source>
           to